Django轮子
**
model_extra_fields.py
**
import abc
from functools import partial
from bmiss.db.models import prefetch_related_objects
class InitialDataBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def get_data(self):
pass
class ModelExtraFieldsBase(object):
"""
可配置扩展字段基类
"""
def __init__(self, mappings, obj_list, ser_class=None):
"""
:param mappings:
{
'display_field_name': [
relation,
field name/get field data callable,
{
"convert": result convert callable,
"default": field default value, default None,
"initial": initial data class --> see example: ReportData
}
],
---- examples ----
'work_order_codes': ['scannerplanmerge_set__work_job__parent_order', self._get_merged_order_codes],
'rgb': ['work_order__color', 'rgb', {'default': 0, 'convert': convert_func}],
if you define:
class ReportData:
def __init__(self, work_order_ids):
....
# you must implement this method
def get_data(self):
result = {
'abc': 1
}
return result
in mappings you have:
'report_data': ['some_relations', self._get_func, {'initial': ReportData}]
then after initializing the child class of ModelExtraFieldsBase, you can use:
b = self.initials['abc'] + 1
now the value of variable b is 2
}
:param obj_list: objects list that need extra fields of its relational objects
"""
self.expand_mappings = mappings
self.objs = obj_list
self.serializer_class = ser_class
self.initials = {}
self.global_initial()
def global_initial(self):
initial_classes = set()
initials = []
for name, config in self.expand_mappings.items():
if isinstance(config, list) and len(config) > 2:
c = config[2].get("initial")
if c is None:
continue
if isinstance(c, partial):
_class = c.func
else:
_class = c
if _class not in initial_classes:
initial_classes.add(_class)
initials.append(c)
fields = set()
for c in initials:
if isinstance(c, partial):
data = c().get_data()
else:
data = c(self.objs).get_data()
for field_name, value in data.items():
if field_name in fields:
raise ValueError(
"field name {} conflict, please rename initial data class {} "
"method get_data return values field name".format(
field_name, c.__name__
)
)
self.initials.update(data)
@property
def data(self):
objs = self.get_flatten_prefetch_related_objects()
result = []
for obj in objs:
data = self.get_instance_fields(obj)
result.append(data)
return result
def get_flatten_prefetch_related_objects(self, fields=None):
if fields is None:
fields = self.default_fields()
if not fields or not isinstance(fields, list):
fields = self.default_fields()
args = self._get_query_args(fields)
fields = self._get_relation_field_names(args)
prefetch_related_objects(self.objs, *args)
flatten_func = partial(self._flatten_instance, fields=fields)
return list(map(flatten_func, self.objs))
def _get_query_args(self, display_fields):
args = set()
expand_fields = []
for field_name in display_fields:
if not isinstance(self.expand_mappings[field_name], list):
continue
relations = self.expand_mappings[field_name][0]
if relations == "self":
continue
elif isinstance(relations, list):
expand_fields.extend(relations)
elif isinstance(relations, str):
expand_fields.append(relations)
expand_fields.sort(key=lambda e: -len(e))
for arg in expand_fields:
if arg in args or any([k.startswith("{}__".format(arg)) for k in args]):
continue
else:
args.add(arg)
return args
def _get_relation_field_names(self, relations):
result = set()
for e in relations:
result.add(e)
count = e.count("__")
for i in range(count):
elem, *_ = e.rsplit("__", i + 1)
result.add(elem)
return result
def _flatten_instance(self, instance, fields):
for f in fields:
name = f
relations = f.split("__")
obj = instance
for r in relations:
if hasattr(obj, r):
obj = getattr(obj, r)
else:
obj = None
break
setattr(instance, name, obj)
return instance
def get_instance_fields(self, obj, fields=None):
if fields is None:
fields = self.default_fields()
if self.serializer_class:
result = self.serializer_class(obj).data
else:
result = {}
for f in fields:
if f not in self.expand_mappings:
continue
if not isinstance(self.expand_mappings[f], list):
v = self.expand_mappings[f]
if callable(v):
result[f] = v(obj)
continue
result[f] = v
continue
name, get_name, *kwargs = self.expand_mappings[f]
if isinstance(get_name, str):
if name == "self":
result[f] = getattr(obj, get_name)
else:
relate_obj = getattr(obj, name)
result[f] = getattr(relate_obj, get_name) if relate_obj else None
elif callable(get_name):
result[f] = get_name(obj)
default_value = kwargs[0].get("default", None) if len(kwargs) else None
if result[f] is None:
result[f] = default_value
convert = kwargs[0].get("convert") if len(kwargs) else None
if result[f] is not None and callable(convert):
result[f] = convert(result[f])
return result
def default_fields(self):
return list(self.expand_mappings.keys())
**
query.py
**
-- coding: utf-8 --
from ..registry import foreignkeys
def prefetch_related_objects(instances, *lookups):
_PrefetchManger(instances, lookups)
class _PrefetchManger(object):
def __init__(self, instances, lookups):
self._visit_instances(instances, lookups)
def _visit_instances(self, instances, lookups):
first_level, next_level = _split_lookup(lookups)
if not first_level:
return
if not instances:
return
model = type(instances[0])
fks = foreignkeys.get_foreign_keys(model)
reverse_related_objs = foreignkeys.get_reverse_related_objects(model)
for lookup in first_level:
is_reverse = False
if lookup.endswith("_set"):
is_reverse = True
reverse_relate_model_name = lookup[:-4]
for from_model, rel_info in list(reverse_related_objs.items()):
if from_model.__name__.lower() == reverse_relate_model_name:
lookup_field = "id"
relation_model = from_model
relation_field = rel_info["from_field"]
break
else:
continue
else:
lookup_field = lookup + "_id"
relation_field = "id"
if lookup_field not in fks:
continue
relation_model = fks[lookup_field]["to_model"]
lookup_ids = []
for instance in instances:
lookup_id = getattr(instance, lookup_field)
if lookup_id is not None:
lookup_ids.append(lookup_id)
relation_instances = list(
relation_model.objects.filter(**{"%s__in" % relation_field: lookup_ids})
)
lookup_id_to_relation_instances = {}
for relation_instance in relation_instances:
lookup_id_to_relation_instances.setdefault(
getattr(relation_instance, relation_field), []
).append(relation_instance)
# fill the cache
for instance in instances:
instance_lookup_result = lookup_id_to_relation_instances.get(
getattr(instance, lookup_field), []
)
if not is_reverse:
instance_lookup_result = (
instance_lookup_result[0] if instance_lookup_result else None
)
setattr(instance, lookup, instance_lookup_result)
self._visit_instances(relation_instances, next_level.get(lookup, []))
def _split_lookup(lookups):
first_level_lookup = []
second_level_lookup = {}
for lookup in lookups:
pieces = lookup.split("__", 1)
first = pieces[0]
children = pieces[1:]
if first:
first_level_lookup.append(first)
if children:
second_level_lookup.setdefault(first, []).append(children[0])
first_level_lookup = list(set(first_level_lookup))
return first_level_lookup, second_level_lookup
使用轮子
from diagnosis.utils.model_extra_fields import ModelExtraFieldsBase
class PersonDataFetcher(ModelExtraFieldsBase):
def __init__(self, objs, request, detail=False):
self.request = request
mappings = {
'id': ['self', 'id'],
'name': ['self', 'name'],
'engineer_level': ['self', 'engineer_level'],
'inquiry_attitude': ['self', 'inquiry_attitude'],
'inquiry_consultation': ['self', 'inquiry_consultation'],
'inquiry_status': ['self', 'inquiry_status'],
'tag': ['persontotag_set__tag', self._get_tag],
'inquiry_points': ['self', 'inquiry_points'],
'avatar_url': ['self', 'avatar_url'],
'answer_count': ['answer_set', self._get_answer_count],
'area': ['location_county__city__province', self._get_area],
}
if detail is True:
mappings.update({
'desc': ['self', 'desc'],
'tag_list': ['persontotag_set__tag', self._get_tag_list],
'inquiry_top3': ['evaluated_set__create_company', self._get_inquiry_top3],
'good_consultation': ['evaluated_set', self._get_good_consultation],
'other_consultation': ['evaluated_set', self._get_other_consultation],
})
super(PersonDataFetcher, self).__init__(mappings, objs)
def _get_area(self, instance):
area = ''
try:
province = instance.location_county.city.province.name
city = instance.location_county.city.name
county = instance.location_county.name
area = province + '·' + city + '·' + county
except AttributeError:
return '获取地址信息失败'
# raise NotAreaError('获取地址信息失败')
finally:
return area
def _get_answer_count(self, instance):
try:
answer_count = instance.anwser_set
except AttributeError:
return 0
return len(answer_count)
def _get_inquiry_top3(self, instance):
data = []
try:
evaluated_obj_top3 = list(reversed(instance.evaluated_set))[:3]
for res in evaluated_obj_top3:
company = res.create_company
dic = {
'inquiry_attitude': res.inquiry_attitude,
'use_points': res.use_points,
'inquiry_consultation': res.inquiry_consultation,
'create_time': res.create_time,
'company_name': company.name,
'company_avatar_url': company.avatar_url,
}
data.append(dic)
except Exception:
return data
finally:
return data
def _get_tag_list(self, instance):
person_tag_list = instance.persontotag_set
tag_list = []
if person_tag_list:
for person_tag_obj in person_tag_list:
tag_obj = person_tag_obj.tag
res = {
'tag_id': tag_obj.id,
'tag_name': tag_obj.name,
'tag_count': person_tag_obj.tag_count
}
tag_list.append(res)
return tag_list
def _get_tag(self, instance):
person_tag_list = instance.persontotag_set
tag_list = []
if person_tag_list:
for person_tag_obj in person_tag_list:
tag_name = person_tag_obj.tag.name
tag_list.append(tag_name)
return tag_list
def _get_good_consultation(self, instance):
good_consultation_count = 0
evaluated_obj_list = instance.evaluated_set
if evaluated_obj_list:
for res in evaluated_obj_list:
if res.attitude_consultation_average >= 4:
good_consultation_count += 1
return good_consultation_count
def _get_other_consultation(self, instance):
other_consultation_count = 0
evaluated_obj_list = instance.evaluated_set
if evaluated_obj_list:
for res in evaluated_obj_list:
if res.attitude_consultation_average < 4:
other_consultation_count += 1
return other_consultation_count
在轮子中(表名__表名)双下划线可以在多表查询中代表正向查询
(表名_set)表名单下划线set呆逼啊反向查询
在轮子的使用中,不可以出现ORM查询数据的语句