Logic in models based on object information - django

Is there a way to embed logic in to Django's model layer, using logic from other fields of the object? This sounds complicated, so I wrote some example code. I don't want to use inheritance, all of my fields but one are shared and it would overcomplicate things.
class Creature(models.Model):
numberOfEyes = models.IntegerField()
type = models.CharField(max_length=30, choices=TYPE_CHOICES)
This is just what I am trying to accomplish, not meant to work
if self.numberOfEyes == 1:
TYPE_CHOICES = (
('cyclops', 'cyclops')
)
else:
TYPE_CHOICES = (
# You get the idea
)
Is there a way to embed this logic at the model layer? Or do I have to define this in the form?

I would recommend that you use inheritance if the field really is shared between different models.
However, it looks like you want the choices to be different for different models, so inheritance wouldn't work in this case.
Here are some options:
Just have a different type field for each of your models. If you only have a few models this is the way to go because it's simple.
Muck with undocumented Django internals. i.e. set _choices in __init__() I wouldn't recommend this because if Django changes this variable or how the variable is used, your code will break. Anyways, here's some pseudo-code for this:
class Creature(models.Model):
numberOfEyes = models.IntegerField()
type = models.CharField(max_length=30)
def __init__(self, *args, **kwargs):
super(MyModel, self).__init__(*args, **kwargs)
for f in self._meta.fields:
if f.name == "type":
f._choices = ('choice1', 'choice2')
Use metaclasses to construct the models. Metaclasses are classes used to create classes. I won't even try to come up with the Metaclass pseudo-code for this because it will probably not work. Django has it's own Metaclass and Metaclass inheritance is complicated. But the idea would be to create the type field in your custom Metaclass with a class level variable specifying what choices to use for the type field. Here's pseudo-code for the usage of the custom Metaclass:
class Creature(models.Model):
__metaclass__ = MyMetaClass
TYPE_CHOICES = ('choice1', 'choice2')
numberOfEyes = models.IntegerField()
type = models.CharField(max_length=30)

Related

Django - edit both sides of a many-to-many relation with generic UpdateView

I have a question whether or not it is possible to use the generic UpdateView class to edit "both sides" of a many-to-many relationship.
I have the following classes defined in models.py:
class SomeCategory(models.Model):
code = models.CharField(max_length=5)
name = models.CharField(max_length=40)
class SomeClass(models.Model):
code = models.CharField(max_length=3, unique=True)
name = models.CharField(max_length=30, unique=False)
age = models.IntegerField(null=False)
allowed_categories = models.ManyToManyField(SomeCategory)
These are both dictionary type tables that store sets of configuration data for my application. To allow editing the dictionaries I use simple UpdateViews:
class SomeClassUpdate(UpdateView):
model = SomeClass
template_name = 'admin/edit_class.html'
fields = ['code', 'name', 'age', 'allowed_categories']
ordering = ['code']
This works fine, I get a nice multi-select and everything is perfect. However, I would like to have the possibility to edit the relationship from the side of the SomeCategory table, so I can choose which SomeClass elements are linked to a certain SomeCategory:
class SomeCategoryUpdate(UpdateView):
model = SomeCategory
template_name = 'admin/edit_category.html'
fields = ['code', 'name', ??????? ]
ordering = ['code']
I have tried adding the related_name attribute to the SomeCategory model, but that did not work.
Any ideas if this can be done without using a custom ModelForm?
Key library versions:
Django==1.11.8
psycopg2==2.7.4
PS: this is my very first question asked on stackoverflow, so please let me know if my post is missing any mandatory elements.
Your issue is in the models.py file. You have two classes, but only one of them mentions the other one. You would think that this should be enough since you are using ManyToManyField after all and assume that it would automatically create every connection leading both ways... Unfortunately this is not true. On the database level it does indeed create a separate intermediary table with references to objects in both original tables, but that doesn't mean that both of them will be automatically visible in Django Admin or similar.
If you would attempt to simply create another someclass = models.ManyToManyField(SomeClass) in the SomeCategory class that would fail. Django would try to create another separate intermediary table through which the connection between two main tables is established. But because the name of the intermediary table depends on where you define the ManyToManyField connection, the second table would be created with a different name and everything would just logically collapse (two tables having two separate default ways to have a ManyToMany connection makes no sense).
The solution is to add a ManyToManyField connection to SomeCategory while also referencing that intermediary/through table that was originally created in the SomeClass class.
A couple of notes about Django/python/naming/programming conventions:
Use the name of the table you are referencing to, as the name of the field that is containing the info about that connection. Meaning that SomeClass's field with a link to SomeCategory should be named somecategory instead of allowed_categories.
If the connection is one-to-many - use singular form; if the connection is many-to-many - use plural. Meaning that in this case we should use plural and use somecategories instead of somecategory.
Django can automatically pluralize names, but it does it badly - it simply adds s letter to the end. Mouse -> Mouses, Category -> Categorys. In those kind of cases you have to help it by defining the verbose_name_plural in the special Meta class.
Using references to other classes without extra 's works only if the the class was already defined previously in the code. In the case of two classes referring to each other that is true only one way. The solution is to put the name of the referred class in the quotation marks like 'SomeCategory' instead of SomeCategory. This sort of reference, called a lazy relationship, can be useful when resolving circular import dependencies between two applications. And since by default it's better to keep the style the same and to avoid unnecessary brain energy wasting of "I will decide whether or not to use quotation marks depending on the order the classes have been organized; I will have to redo this quotation marks thingie every time I decide to move some code pieces around" I recommend that you simply use quotation marks every time. Just like when learning to drive a car - it's better to learn to always use turn signals instead of first looking around and making a separate decision of whether someone would benefit from that information.
"Stringifying" (lazy loading) model/class/table name is easy - just add 's around. You would think that stringifying the "through" table reference would work the same easy way. And you would be wrong - it will give you the ValueError: Invalid model reference. String model references must be of the form 'app_label.ModelName'. error. In order to reference the stringified "through" table you need to: (a) add 's around; (b) replace all dots (.) with underscores (_); (c) delete the reference to through!.. So SomeClass.somecategories.through becomes 'SomeClass_somecategories'.
Therefore the solution is this:
class SomeCategory(models.Model):
code = models.CharField(max_length=5)
name = models.CharField(max_length=40)
someclasses = models.ManyToManyField('SomeClass', through='SomeClass_somecategories', blank=True)
class Meta:
verbose_name_plural = 'SomeCategories'
class SomeClass(models.Model):
code = models.CharField(max_length=3, unique=True)
name = models.CharField(max_length=30, unique=False)
age = models.IntegerField(null=False)
somecategories = models.ManyToManyField('SomeCategory')
After this it should be obvious what kind of final changes to make to your UpdateView classes.
You can achieve this in the view and form, without having to specify the additional ManytoMany connections in the
models, using something like the following:
In the View
class SomeClassUpdate(UpdateView):
model = SomeClass
form_class = SomeClassUpdateForm # to specify the form
template_name = 'admin/edit_class.html'
def form_valid(self, form, *args, **kwargs):
initial_somecategorys = SomeCategory.objects.filter(allowed_categories__pk=form.instance.pk)
amended_somecategorys = form.cleaned_data['allowed_categroies']
remove = [x for x in initial_somecategorys if x not in amended_somecategorys]
add = [x for x in amended_somecategorys if x not in initial_somecategorys]
for somecategory in add:
somecategory.allowed_categories.add(form.instance)
somecategory.save()
for somecategory in remove:
somecategory.allowed_categories.remove(form.instance)
somecategory.save()
return super().form_valid(form)
In the Form
The init method at the top pre-populates the form with entries saved on the model.
class SomeClassUpdateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SomeClassUpdateForm, self).__init__(*args, **kwargs)
try:
obj = kwargs['instance']
self.fields["some_categories"].initial = SomeCategory.objects.filter(allowed_categories__pk=form.instance.pk)
except (AttributeError, KeyError): # to catch NoneType if new entry being created.
pass
some_categories = forms.ModelMultipleChoiceField(
required=False,
queryset=SomeCategory.objects.all(),
)
class Meta:
model = SomeClass
fields = [
'some_categories'
..etc
]
This should work. I've writen similar code in one of my projects, and it's working fine. However, I don't know if it's
structurally best to use methods like this and not alter the model relationships or whether it's preferable to
alter the model relationships as outlined in other replies. So I'd be interested to know other peoples views on what
the best approach is.

Custom django foreignfield

Anybody knows how to create a foreignkey field and make it always point to same model, so far I got these.
class PanMachineTimeUnitField(models.ForeignKey):
def __init__(self, **kwargs):
to = 'panbas.PanBasTimeUnit'
kwargs['verbose_name'] = _('Machine Unit')
kwargs['related_name'] = 'machine_unit'
super(PanMachineTimeUnitField, self).__init__(to, **kwargs)
But I got errors when on start.
I aim to use it like,
machine_unit = PanMachineTimeUnitField()
No further declarations needed.
Edit:
I want this because, I will have this foreignkey in quiet a few places. If I want to change the verbose_name of field, I want all of my fields to be affected by this change. Verbose name was an example, it may be an another attribute.
I dont want to use settings py to declare the defaults, either.
I recommend that you use only a simple function to create a similarly pre-configured instance of ForeignKey: (not an instance of subclass of ForeignKey)
def pan_machine_time_unit_field(**kwargs):
othermodel = 'panbas.PanBasTimeUnit'
on_delete = models.DO_NOTHING # or what you need
kwargs['verbose_name'] = 'Machine Unit'
kwargs.setdefault('related_name', '+')
# or: kwargs.setdefault('related_name', "%(app_label)s_%(class)s_related",
return models.ForeignKey(othermodel, on_delete, **kwargs)
class C(models.Model):
machine_unit = pan_machine_time_unit_field()
# or:
# machine_unit = pan_machine_time_unit_field(related_name='klass_c_children')
The related_name attribute is a name used for backward relation from the target object of othermodel to all objects that reference it. That name must be unique on othermodel ('panbas.PanBasTimeUnit', usually something with app and class name that is unique enough) or that name can be '+' if you don't want to create a backward relationship query set. Both variants are implied in the example. Also remember on_delete.
If you would really need to create a subclass (which makes sense if more methods need be customized), you must also define a deconstruct method for migrations. It would be complicated if you need to modify such subclass later. It can be never removed, renamed etc. due to migrations on a custom field. On the other hand, if you create a simple instance of ForeignKey directly by a function, all about migrations can be ignored.
EDIT
Alternatively you can create an abstract base model with that field and create new models by inheritance or multiple inheritance:
class WithPanBasTimeUnit(models.Model):
machine_unit = models.ForeignKey(
'panbas.PanBasTimeUnit',
models.DO_NOTHING,
verbose_name=_('Machine Unit'),
related_name='%(app_label)s_%(class)s_related'
)
class Meta:
abstract = True
class ExampleModel(WithPanBasTimeUnit, ...or more possible base models...):
... other fields
This solution (inspired by an invalid soution Ykh) useful if you want to add a method to models with that field or to add more fields together, otherwise the original solution is easier.
class PanBasTimeUnit(models.Model):
machine_unit = models.ForeignKey('self', blank=True, null=True,
verbose_name=u'parent')
use 'self' or 'panbas.PanBasTimeUnit' will fine.
You can not have several Foreign Keys to a model with same related_name.
Indeed, on a PanBasTimeUnit instance, which manager should Django return when calling <instance>.machine_unit? This is why you have to be carefull on related models and abstract classes.
It should work fine if you remove kwargs['related_name'] = 'machine_unit' in your code, and replace it with kwargs['related_name'] = "%(app_label)s_%(class)s_related" or something similar.
A slight modification in your attempt should do your work.
class PanMachineTimeUnitField(models.ForeignKey):
def __init__(self, **kwargs):
kwargs["to"] = 'panbas.PanBasTimeUnit'
kwargs['verbose_name'] = _('Machine Unit')
kwargs['related_name'] = 'machine_unit'
super(PanMachineTimeUnitField, self).__init__(**kwargs)
why not use directly machine_unit = models.ForeignKey(panbas.PanBasTimeUnit, verbose_name=_('Machine Unit'), related_name='machine_unit')) ?

attach mixin to model when it is created based on a field?

Can I attach a Mixin automatically to a Model when it is created based on a field?
I have been looking at the 3 options of Model inheritance https://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance but I am not sure how to achieve what I am looking to do and if it is possible.
I just want ONE table where I will store all the fields, no matter if one field is just for
one of the type.
This table will have a type field.
class Example(models.Model):
objects = ExampleManager()
description = models.CharField(max_length=100, blank=True, null=True)
# All the fields needed for all the types, the common and specific fields
type = models.CharField(max_length=2, choices=('T1','T2'))
Now, when I do:
all = Example.objects.all()
for a in all:
a.quack()
I would like that quack is different according to the type field.
But I would like to avoid to write a quack function in the Example class model with several if.
I would like to encapsulate into classes the logic for each subtype.
Can I attach a Mixin automatically to the Model when it is created based on the type field?
Thanks
You can do this by defining a field subclass with a contribute_to_class method. That's how fields with the choices attribute define a get_FOO_display method on their models.
contribute_to_class is passed the model class, and the name this field is being defined as. You can use the model class to add extra methods. It might work something like this:
class DuckField(models.CharField):
def contribute_to_class(self, cls, name):
super(DuckField, self).contribute_to_class(cls, name)
# method to be added
def quack(self):
return '%s quacks!' % self
# add it to the model
cls.quack = quack

Tastypie accessing fields from inherited models

Is it possible to include fields on related models, using tastypie?
As per my models below: if I persist one VideoContent and one TextContent instance to the DB, I can then get 2 objects back from my Content resource, however none of the additional fields are available.
Is it possible to include fields from related models (in this instance, the video url and the text content) and will that cater for adding more Content types in the future without having to rewrite the Content Resource, or am I coming at this from the wrong direction?
The goal is to be able to extend this with more ContentTypes without having to make changes to the Content resource (assuming it's possible to get it working in the first place)
Models.py:
class Content(models.Model):
parent = models.ForeignKey('Content', related_name='children', null=True, blank=True)
class TextContent(Content):
text = models.CharField(max_length=100)
class VideoContent(Content):
url = models.CharField(max_length=1000)
And then my resources:
class ContentResource(ModelResource):
children = fields.ToManyField('myapp.api.resources.ContentResource', 'children', null=True, full=True)
class Meta:
resource_name = 'content'
queryset = ContentResource.objects.all()
authorization = Authorization()
always_return_data = True
I found a good solution in another answer
Populating a tastypie resource for a multi-table inheritance Django model
I've run into the same problem - although I'm still in the middle of solving it. Two things that I've figured out so far:
django-model-utils provides an inheritence manager that lets you use the abstract base class to query it's table and can automatically downcast the query results.
One thing to look at is the dehydrate/rehydrate methods available to Resource classes.
This is what I did:
class CommandResource(ModelResource):
class Meta:
queryset = Command.objects.select_subclasses().all()
That only gets you half way - the resource must also include the dehydrate/rehydrate stuff because you have to manually package the object up for transmission (or recieving) from the user.
The thing I'm realizing now is that this is super hacky and there's gotta be a better/cleaner way provided by tastypie - they can't expect you to have to do this type of manual repackaging in these types of situations - but, maybe they do. I've only got about 8 hours of experience with tastypie # this point so if I'm explaining this all wrong perhaps some nice stackoverflow user can set me straight. :D :D :D
I had the same requirement and finally solved it.
I didn't like the answer given in the above link because I didn't like the idea of combining queryset and re-sorting.
Apparently, you can inherit multiple resources.
By subclassing multiple resources, you include the fields of the resources.
And since those fields are unique to each resource, I made them nullable in the init.
wonder if there's a way to list the parents only once. (There are two now. One for subclassing, and one in meta)
class SudaThreadResource(ThreadResource):
def __init__(self, *args, **kwargs):
super(SudaThreadResource, self).__init__(*args, **kwargs)
for field_name, field_object in self.fields.items():
# inherited_fields can be null
if field_name in self.Meta.inherited_fields:
field_object.null=True
class Meta(ThreadResource.Meta):
resource_name = 'thread_suda'
usedgoodthread_fields = UsedgoodThreadResource.Meta.fields[:]
userdiscountinfothread_fields = UserDiscountinfoThreadResource.Meta.fields[:]
staffdiscountinfothread_fields = StaffDiscountinfoThreadResource.Meta.fields[:]
bitem_checklistthread_fields = BitemChecklistThreadResource.Meta.fields[:]
parent_field_set = set(ThreadResource.Meta.fields[:])
field_set = set(
set(usedgoodthread_fields) |
set(userdiscountinfothread_fields) |
set(staffdiscountinfothread_fields) |
set(bitem_checklistthread_fields)
)
fields = list(field_set)
inherited_fields = list(field_set - parent_field_set)
queryset = forum_models.Thread.objects.not_deleted().exclude(
thread_type__in=(forum_const.THREAD_TYPE_MOMSDIARY, forum_const.THREAD_TYPE_SOCIAL_DISCOUNTINFO)
).select_subclasses()

Model "Behaviors" in Django

I'm new to Django, moved from PHP with Propel ORM engine.
So here is what I am currently doing in Django. My website has several models like
Book,
Publisher,
Comment,
Article and so on (it's not the point)
Each of them can can be
liked or disliked by a user (only once) changing the model's rating by +1 or -1.
In terms of PHP i would create a behavior, for ex. "Rateable" which would add some fields and methods to the original model and query class (like get_rating(), order_by_rating(), etc) and create a separate table for each model, for ex. book_rating_history which would hold all of the ratings for each object, to determine if the user can or can't change the rating (or show all object's ratings, if necessary). So all I would need to do is to specify the "Rateable" behavior in the model declaration, and that's all. Everything else is done automatically.
The question is - how to solve this in Django? How to model correctly? Which techniques do you use in similar cases?
You'll want to store ratings and books separately, something like this (untested).
from django.contrib.auth.models import User
from django.db import models
from django.db.models import Sum
class BookRating(models.Model):
user = models.ForeignKey(User)
book = models.ForeignKey('Book')
# you'll want to enforce that this is only ever +1 or -1
rating = models.IntegerField()
class Meta:
unique_together = (('user', 'book'),)
class Book(models.Model):
title = models.CharField(max_length = 50)
def rating(self):
return BookRating.objects.filter(book = self).aggregate(Sum('rating'))
unique_together enforces that each user can only rate a given book once.
You can then use this something like:
book = Book.objects.get(pk = 1)
rating = book.rating()
Add a comment if you have problems with this - I've not tested it, but hopefully this is enough to get you started.
You can probably avoid each object (books, publishers, comments, articles) having a separate rating object using content types, if you want.
Alternatively, you might consider looking at existing reusable apps that handle liking, like django-likes or phileo.
You can define special methods (for example vote, get_rating, etc.) only onсe in abstract model and then create your "Rateable" models using this one.
class Rateable(models.Model):
class Meta:
abstract = True
def vote(self, *args, **kwargs):
...
def rating(self, *args, **kwargs):
...
class Book(Rateable):
...
Also it is better to use single model for storing rating data witch content types as noticed Dominic Rodger