python - Django: Parent Model with multiple child model types -
i've created set of django model cms show series of product
s.
each page contains series of rows, have generic
class productrow(models.model): slug = models.slugfield(max_length=100, null=false, blank=false, unique=true, primary_key=true) name = models.charfield(max_length=200,null=false,blank=false,unique=true) active = models.booleanfield(default=true, null=false, blank=false)
then have series of children of model, different types of row:
class productbanner(productrow): wide_image = models.imagefield(upload_to='product_images/banners/', max_length=100, null=false, blank=false) top_heading_text = models.charfield(max_length=100, null=false, blank=false) main_heading_text = models.charfield(max_length=200, null=false, blank=false) ... class productmagazinerow(productrow): title = models.charfield(max_length=50, null=false, blank=false) show_descriptions = models.booleanfield(null=false, blank=false, default=false) panel_1_product = models.foreignkey(product, related_name='+', null=false, blank=false) panel_2_product = models.foreignkey(product, related_name='+', null=false, blank=false) panel_3_product = models.foreignkey(product, related_name='+', null=false, blank=false) ... class producttextgridrow(productrow): title = models.charfield(max_length=50, null=false, blank=false) col1_title = models.charfield(max_length=50, null=false, blank=false) col1_product_1 = models.foreignkey(product, related_name='+', null=false, blank=false) col1_product_2 = models.foreignkey(product, related_name='+', null=false, blank=false) col1_product_3 = models.foreignkey(product, related_name='+', null=false, blank=false) ...
and on.
then in productpage
have series of productrow
s:
class productpage(models.model): slug = models.slugfield(max_length=100, null=false, blank=false, unique=true, primary_key=true) name = models.charfield(max_length=200, null=false, blank=false, unique=true) title = models.charfield(max_length=80, null=false, blank=false) description = models.charfield(max_length=80, null=false, blank=false) row_1 = models.foreignkey(productrow, related_name='+', null=false, blank=false) row_2 = models.foreignkey(productrow, related_name='+', null=true, blank=true) row_3 = models.foreignkey(productrow, related_name='+', null=true, blank=true) row_4 = models.foreignkey(productrow, related_name='+', null=true, blank=true) row_5 = models.foreignkey(productrow, related_name='+', null=true, blank=true)
the problem have got, want allow 5 rows in productpage
of different child types of productrow
. when iterate on them such
in views.py
:
product_page_rows = [product_page.row_1,product_page.row_2,product_page.row_3,product_page.row_4,product_page.row_5]
and in template:
{% row in product_page_rows %} <pre>{{ row.xxxx }}</pre> {% endfor %}
i cannot reference child field xxxx
.
i tried adding "type
()" method both parent , children, try , distinguish class each row is:
class productrow(models.model): ... @classmethod def type(cls): return "generic"
and
class producttextgridrow(tourrow): ... @classmethod def type(cls): return "text-grid"
but if change xxxx
.type()
in template shows "generic"
every item in list (i had defined variety of row types in data), guess coming productrow
rather appropriate child type. can find no way children accessible correct child type rather parent type, or determine child type (i tried catch
ing attributeerror
well, didn't help).
can advise how can handle list of varied model types of contain common parent, , able access fields of appropriate child model type?
this (read "always") bad design have this:
class mymodel(models.model): ... row_1 = models.foreignkey(...) row_2 = models.foreignkey(...) row_3 = models.foreignkey(...) row_4 = models.foreignkey(...) row_5 = models.foreignkey(...)
it not scalable. if ever want allow 6 rows or 4 rows instead of 5, 1 day (who knows?), have add/delete new row , alter database scheme (and handle existing objects had 5 rows). , it's not dry, amount of code depends on number of rows handle , involves lot of copy-pasting.
this become clear bad design if wonder how if had handle 100 rows instead of 5.
you have use manytomanyfield()
, custom logic ensure there @ least 1 row, , @ 5 rows.
class productpage(models.model): ... rows = models.manytomanyfield(productrow)
if want rows ordered, can use explicit intermediate model this:
class productpagerow(models.model): class meta: order_with_respect_to = 'page' row = models.foreignkey(productrow) page = models.foreignkey(productpage) class productpage(models.model): ... rows = model.manytomanyfield(productrow, through=productpagerow)
i want allow n
rows (let's 5), implement own order_with_respect_to
logic:
from django.core.validators import maxvaluevalidator class productpagerow(models.model): class meta: unique_together = ('row', 'page', 'ordering') max_rows = 5 row = models.foreignkey(productrow) page = models.foreignkey(productpage) ordering = models.positivesmallintegerfield( validators=[ maxvaluevalidator(max_rows - 1), ], )
the tuple ('row', 'page', 'ordering')
uniqueness being enforced, , ordering being limited 5 values (from 0 4), there can't more 5 occurrences of couple ('row', 'page')
.
however, unless have reason make 100% sure there no way add more n
rows in database mean (including direct sql query input on dbms console), there no need "lock" level.
it "untrusted" user able update database through html form inputs. , can use formsets force both minimum , maximum number of rows when filling form.
note: applies other models. bunch of fields named
foobar_n
,n
incrementing integer, betrays bad database design.
yet, not fix issue.
the easiest (read "the first comes mind") way child model instance parent model instance loop on each possible child model until instance matches.
class productrow(models.model): ... def get_actual_instance(self): if type(self) != productrow: # if it's not productrow, child return self attr_name = '{}_ptr'.format(productrow._meta.model_name) possible_class in self.__subclasses__(): field_name = possible_class._meta.get_field(attr_name).related_query_name() try: return getattr(self, field_name) except possible_class.doesnotexist: pass # if no child found, productrow return self
but involves hit database each try. , still not dry. efficient way add field tell type of child:
from django.contrib.contenttypes.models import contenttype class productrow(models.model): ... actual_type = models.foreignkey(contenttype, editable=false) def save(self, *args, **kwargs): if self._state.adding: self.actual_type = contenttype.objects.get_for_model(type(self)) super().save(*args, **kwargs) def get_actual_instance(self): my_info = (self._meta.app_label, self._meta.model_name) actual_info = (self.actual_type.app_label, self.actual_type.model) if type(self) != productrow or my_info == actual_info: # if actual instance return self # otherwise attr_name = '{}_ptr_id'.format(productrow._meta.model_name) return self.actual_type.get_object_for_this_type(**{ attr_name: self.pk, })
Comments
Post a Comment