Python programmatically define variables in class body - python

I have two django models, model Person and model PersonTemplate that have the exact same field names & types, with the exception being that some of the fields are required in Person, whereas they are not in PersonTemplate. It's easy enough to just copy paste the models into both of these classes, but that involves a lot of hard-coding and if I edit one model and forget the other, stuff might break.
The solution I have in mind is to define the django model fields in a function that takes a boolean argument, whether or not the fields are required. Something like
def get_fields(is_required=True):
first_name = models.CharField(max_length=128, verbose_name=_("First Name"),
blank=is_required, null=is_required)
last_name = models.CharField(max_length=128, verbose_name=_("Last Name"),
blank=is_required, null=is_required)
return locals()
class Person(models.Model):
vars = get_fields(True)
class PersonTemplate(models.Model):
vars = get_fields(False)
What I don't know how to do is get the local variables from get_fields into the class body of the models. Anybody have any ideas, or suggestions for a better way to implement these models?

Django has Abstract models which you can use do define repetitive fields and methods and then inherit then to an actual model of your choice.
And for dynamic required field, you can set their values using model methods and override those methods in child model. See this answer.
Something like this should work:
class AbstractPerson(models.Model):
#staticmethod
def get_first_name_requirement():
return True
#staticmethod
def get_last_name_requirement():
return True
first_name = models.CharField(max_length=128, verbose_name=_("First Name"),
blank=get_first_name_required.__func__(), null=get_first_name_required.__func__())
last_name = models.CharField(max_length=128, verbose_name=_("Last Name"),
blank=get_last_name_required.__func__(), null=get_last_name_required.__func__())
class Meta(object):
abstract = True
class Person(AbstractPerson):
#staticmethod
def get_first_name_requirement():
return True
#staticmethod
def get_last_name_requirement():
return True
class PersonTemplate(AbstractPerson):
#staticmethod
def get_first_name_requirement():
return False
#staticmethod
def get_last_name_requirement():
return False
I am sure there must be more elegant way for dynamically assigning null values. But this too will get the job done.

To achieve you idea you need to use metaclasses. But as usual this is bad idea.
Why do not you look at Django Abstract models and inheritance? This will solve your question in much more readable and simple way.

Related

What is the use of writing custom QuerySet classes in Django

I was going through Django documentation for model managers. I saw that in this section i saw that that model.QuerySet class is overriden by a custom QuerySet class which has two methods which returns the queryset by filtering role='A' or role='E'.
class PersonQuerySet(models.QuerySet):
def authors(self):
return self.filter(role='A')
def editors(self):
return self.filter(role='E')
class PersonManager(models.Manager):
def get_queryset(self):
return PersonQuerySet(self.model, using=self._db)
def authors(self):
return self.get_queryset().authors()
def editors(self):
return self.get_queryset().editors()
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
people = PersonManager()
But I don't understand what was the need of filtering in the QuerySet class when we could have done that in model manager itself like this.
class PersonManager(models.manager):
def authors(self):
return self.filter(role='A')
def editors(self):
return self.filter(role='E')
Please help to understand the difference. Thank you in advance.
While making queries using Django's ORM it is quite common to see people chaining methods on a queryset, example:
queryset.filter(...).annotate(...).order_by(...)
Each of the method here returns an instance of QuerySet upon which we can apply more methods.
Now let us assume we were to use your solution and implement our methods in the model manager. Let us consider the below snippet in that case:
print(type(Person.objects)) # prints the manager class
print(type(Person.objects.all())) # prints the queryset class
Person.objects.authors() # Succeeds
Person.objects.all().authors() # likely (might be some other) raises an AttributeError
Whereas if we had done this with a custom queryset the second queryset too would have worked. This is because if we declared the method on the manager the queryset will not really have it, and hence our chaining of the method would fail.

Django: how to use a DB parameter in a default attribute of model field?

I have created a (kind of) singleton to put all the app parameters in my database:
class SingletonModel(models.Model):
def save(self, *args, **kwargs):
self.pk = 1
super(SingletonModel, self).save(*args, **kwargs)
#classmethod
def load(cls):
return cls.objects.all().get()
class Meta:
abstract = True
class AppParameters(SingletonModel, models.Model):
DEFAULT_BALANCE_ALERT_THRESHOLD = models.PositiveIntegerField(default=5)
# other parameters...
It worked pretty well, until I tried to use one of these parameters in a default attribute of a model field:
class Convive(models.Model):
balance_alert_threshold = models.IntegerField(
default=AppParameters.load().DEFAULT_BALANCE_ALERT_THRESHOLD,
blank=True,
null=True)
This seemed to work too, but when I use a script to reinitialise local data, the first manage.py migrate produce a DoesNotExist since my Singleton does not exist yet.
It happens because of a file importing Convive model.
How would you solve this?
Is there a way to "delay" the evaluation of the default field?
Thanks.
EDIT
After posting this, I think that if my code processes db queries at import time, something may be wrong with it...
Create a method that returns the default value,
def get_default_balance_alert_threshold():
return AppParameters.load().DEFAULT_BALANCE_ALERT_THRESHOLD
then use that method as your default.
class Convive(models.Model):
balance_alert_threshold = models.IntegerField(
default=get_default_balance_alert_threshold,
blank=True,
null=True,
)

Any way to use a Django Model as an Interface?

I have a Base class for many Subclasses, and the only thing changing within the subclasses is a certain method (the template pattern). However I am stuck and can't get it to work.
class Base(models.Model):
_value = models.CharField(max_length=200)
_name = models.CharField(max_length=200)
user = models.ForeignKey(User, related_name="some_set")
#used as a property
def value():
def fget(self):
self.refresh()
return self._value
def refresh(self):
raise NotImplementedError("..")
class Subclass1(Base):
def refresh(self):
self._value = some_val
class Subclass2(Base):
def refresh(self):
self._value = some_other_val
I would love to be able to treat the entire related set as the same entity, and call the value property on each, with each deferring to its own implemented version of refresh, i.e.
for x in user.some_set.all():
print x.value
but at this point it doesn't seem possible, even with removing refresh in the superclass. I've also thought of using the Strategy pattern and use a ForeignKey relationship to call the method, but I would still have to have a base class in the ForeignKey that the subclasses derive from.
use Proxy Models
from the doc
Sometimes, however, you only want to change the Python behavior of a model – perhaps to change the default manager, or add a new method.
This is what proxy model inheritance is for: creating a proxy for the original model. You can create, delete and update instances of the proxy model and all the data will be saved as if you were using the original (non-proxied) model. The difference is that you can change things like the default model ordering or the default manager in the proxy, without having to alter the original.
I might be missing the point, but have you tried Django Model Utils?
https://bitbucket.org/carljm/django-model-utils/src
If you look at the inheritance manager and make the relevant changes to your model, you should then be able to query as per:
entities = Base.objects.filter(user=my_user_obj).select_subclasses()
for entity in entities:
print entity.value
I ended up using the Strategy pattern with a GenericForeignKey
class Base(models.Model):
_name = models.CharField(max_length=200)
user = models.ForeignKey(User, related_name="some_set")
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
strategy = GenericForeignKey()
#used as a property
def value():
def fget(self):
return self.strategy.value
class Strategy1(models.Model):
#some other definitions
def value():
def fget(self):
return some_var
class Strategy2(models.Model):
#some other definitions
def value():
def fget(self):
return some_other_var
Which allowed me to do for x in user.some_set.all(): print x.value

django abstract models versus regular inheritance

Besides the syntax, what's the difference between using a django abstract model and using plain Python inheritance with django models? Pros and cons?
UPDATE: I think my question was misunderstood and I received responses for the difference between an abstract model and a class that inherits from django.db.models.Model. I actually want to know the difference between a model class that inherits from a django abstract class (Meta: abstract = True) and a plain Python class that inherits from say, 'object' (and not models.Model).
Here is an example:
class User(object):
first_name = models.CharField(..
def get_username(self):
return self.username
class User(models.Model):
first_name = models.CharField(...
def get_username(self):
return self.username
class Meta:
abstract = True
class Employee(User):
title = models.CharField(...
I actually want to know the difference between a model class that
inherits from a django abstract class (Meta: abstract = True) and a
plain Python class that inherits from say, 'object' (and not
models.Model).
Django will only generate tables for subclasses of models.Model, so the former...
class User(models.Model):
first_name = models.CharField(max_length=255)
def get_username(self):
return self.username
class Meta:
abstract = True
class Employee(User):
title = models.CharField(max_length=255)
...will cause a single table to be generated, along the lines of...
CREATE TABLE myapp_employee
(
id INT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
...whereas the latter...
class User(object):
first_name = models.CharField(max_length=255)
def get_username(self):
return self.username
class Employee(User):
title = models.CharField(max_length=255)
...won't cause any tables to be generated.
You could use multiple inheritance to do something like this...
class User(object):
first_name = models.CharField(max_length=255)
def get_username(self):
return self.username
class Employee(User, models.Model):
title = models.CharField(max_length=255)
...which would create a table, but it will ignore the fields defined in the User class, so you'll end up with a table like this...
CREATE TABLE myapp_employee
(
id INT NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
An abstract model creates a table with the entire set of columns for each subchild, whereas using "plain" Python inheritance creates a set of linked tables (aka "multi-table inheritance"). Consider the case in which you have two models:
class Vehicle(models.Model):
num_wheels = models.PositiveIntegerField()
class Car(Vehicle):
make = models.CharField(…)
year = models.PositiveIntegerField()
If Vehicle is an abstract model, you'll have a single table:
app_car:
| id | num_wheels | make | year
However, if you use plain Python inheritance, you'll have two tables:
app_vehicle:
| id | num_wheels
app_car:
| id | vehicle_id | make | model
Where vehicle_id is a link to a row in app_vehicle that would also have the number of wheels for the car.
Now, Django will put this together nicely in object form so you can access num_wheels as an attribute on Car, but the underlying representation in the database will be different.
Update
To address your updated question, the difference between inheriting from a Django abstract class and inheriting from Python's object is that the former is treated as a database object (so tables for it are synced to the database) and it has the behavior of a Model. Inheriting from a plain Python object gives the class (and its subclasses) none of those qualities.
The main difference is how the databases tables for the models are created.
If you use inheritance without abstract = True Django will create a separate table for both the parent and the child model which hold the fields defined in each model.
If you use abstract = True for the base class Django will only create a table for the classes that inherit from the base class - no matter if the fields are defined in the base class or the inheriting class.
Pros and cons depend on the architecture of your application.
Given the following example models:
class Publishable(models.Model):
title = models.CharField(...)
date = models.DateField(....)
class Meta:
# abstract = True
class BlogEntry(Publishable):
text = models.TextField()
class Image(Publishable):
image = models.ImageField(...)
If the Publishable class is not abstract Django will create a table for publishables with the columns title and date and separate tables for BlogEntry and Image. The advantage of this solution would be that you are able to query across all publishables for fields defined in the base model, no matter if they are blog entries or images. But therefore Django will have to do joins if you e.g. do queries for images...
If making Publishable abstract = True Django will not create a table for Publishable, but only for blog entries and images, containing all fields (also the inherited ones). This would be handy because no joins would be needed to an operation such as get.
Also see Django's documentation on model inheritance.
Just wanted to add something which I haven't seen in other answers.
Unlike with python classes, field name hiding is not permited with model inheritance.
For example, I have experimented issues with an use case as follows:
I had a model inheriting from django's auth PermissionMixin:
class PermissionsMixin(models.Model):
"""
A mixin class that adds the fields and methods necessary to support
Django's Group and Permission model using the ModelBackend.
"""
is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.'))
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'))
user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.')
class Meta:
abstract = True
# ...
Then I had my mixin which among other things I wanted it to override the related_name of the groups field. So it was more or less like this:
class WithManagedGroupMixin(object):
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
related_name="%(app_label)s_%(class)s",
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'))
I was using this 2 mixins as follows:
class Member(PermissionMixin, WithManagedGroupMixin):
pass
So yeah, I expected this to work but it didn't.
But the issue was more serious because the error I was getting wasn't pointing to the models at all, I had no idea of what was going wrong.
While trying to solve this I randomly decided to change my mixin and convert it to an abstract model mixin. The error changed to this:
django.core.exceptions.FieldError: Local field 'groups' in class 'Member' clashes with field of similar name from base class 'PermissionMixin'
As you can see, this error does explain what is going on.
This was a huge difference, in my opinion :)
The main difference is when you inherit the User class. One version will behave like a simple class, and the other will behave like a Django modeel.
If you inherit the base "object" version, your Employee class will just be a standard class, and first_name won't become part of a database table. You can't create a form or use any other Django features with it.
If you inherit the models.Model version, your Employee class will have all the methods of a Django Model, and it will inherit the first_name field as a database field that can be used in a form.
According to the documentation, an Abstract Model "provides a way to factor out common information at the Python level, whilst still only creating one database table per child model at the database level."
I will prefer the abstract class in most of the cases because it does not create a separate table and the ORM does not need to create joins in the database. And using abstract class is pretty simple in Django
class Vehicle(models.Model):
title = models.CharField(...)
Name = models.CharField(....)
class Meta:
abstract = True
class Car(Vehicle):
color = models.CharField()
class Bike(Vehicle):
feul_average = models.IntegerField(...)

Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?

I have a Person model that has a foreign key relationship to Book, which has a number of fields, but I'm most concerned about author (a standard CharField).
With that being said, in my PersonAdmin model, I'd like to display book.author using list_display:
class PersonAdmin(admin.ModelAdmin):
list_display = ['book.author',]
I've tried all of the obvious methods for doing so, but nothing seems to work.
Any suggestions?
As another option, you can do lookups like:
#models.py
class UserAdmin(admin.ModelAdmin):
list_display = (..., 'get_author')
def get_author(self, obj):
return obj.book.author
get_author.short_description = 'Author'
get_author.admin_order_field = 'book__author'
Since Django 3.2 you can use display() decorator:
#models.py
class UserAdmin(admin.ModelAdmin):
list_display = (..., 'get_author')
#admin.display(ordering='book__author', description='Author')
def get_author(self, obj):
return obj.book.author
Despite all the great answers above and due to me being new to Django, I was still stuck. Here's my explanation from a very newbie perspective.
models.py
class Author(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=255)
admin.py (Incorrect Way) - you think it would work by using 'model__field' to reference, but it doesn't
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ['title', 'author__name', ]
admin.site.register(Book, BookAdmin)
admin.py (Correct Way) - this is how you reference a foreign key name the Django way
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ['title', 'get_name', ]
def get_name(self, obj):
return obj.author.name
get_name.admin_order_field = 'author' #Allows column order sorting
get_name.short_description = 'Author Name' #Renames column head
#Filtering on side - for some reason, this works
#list_filter = ['title', 'author__name']
admin.site.register(Book, BookAdmin)
For additional reference, see the Django model link here
Like the rest, I went with callables too. But they have one downside: by default, you can't order on them. Fortunately, there is a solution for that:
Django >= 1.8
def author(self, obj):
return obj.book.author
author.admin_order_field = 'book__author'
Django < 1.8
def author(self):
return self.book.author
author.admin_order_field = 'book__author'
Please note that adding the get_author function would slow the list_display in the admin, because showing each person would make a SQL query.
To avoid this, you need to modify get_queryset method in PersonAdmin, for example:
def get_queryset(self, request):
return super(PersonAdmin,self).get_queryset(request).select_related('book')
Before: 73 queries in 36.02ms (67 duplicated queries in admin)
After: 6 queries in 10.81ms
For Django >= 3.2
The proper way to do it with Django 3.2 or higher is by using the display decorator
class BookAdmin(admin.ModelAdmin):
model = Book
list_display = ['title', 'get_author_name']
#admin.display(description='Author Name', ordering='author__name')
def get_author_name(self, obj):
return obj.author.name
According to the documentation, you can only display the __unicode__ representation of a ForeignKey:
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display
Seems odd that it doesn't support the 'book__author' style format which is used everywhere else in the DB API.
Turns out there's a ticket for this feature, which is marked as Won't Fix.
I just posted a snippet that makes admin.ModelAdmin support '__' syntax:
http://djangosnippets.org/snippets/2887/
So you can do:
class PersonAdmin(RelatedFieldAdmin):
list_display = ['book__author',]
This is basically just doing the same thing described in the other answers, but it automatically takes care of (1) setting admin_order_field (2) setting short_description and (3) modifying the queryset to avoid a database hit for each row.
There is a very easy to use package available in PyPI that handles exactly that: django-related-admin. You can also see the code in GitHub.
Using this, what you want to achieve is as simple as:
class PersonAdmin(RelatedFieldAdmin):
list_display = ['book__author',]
Both links contain full details of installation and usage so I won't paste them here in case they change.
Just as a side note, if you're already using something other than model.Admin (e.g. I was using SimpleHistoryAdmin instead), you can do this: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).
You can show whatever you want in list display by using a callable. It would look like this:
def book_author(object):
return object.book.author
class PersonAdmin(admin.ModelAdmin):
list_display = [book_author,]
This one's already accepted, but if there are any other dummies out there (like me) that didn't immediately get it from the presently accepted answer, here's a bit more detail.
The model class referenced by the ForeignKey needs to have a __unicode__ method within it, like here:
class Category(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
That made the difference for me, and should apply to the above scenario. This works on Django 1.0.2.
If you have a lot of relation attribute fields to use in list_display and do not want create a function (and it's attributes) for each one, a dirt but simple solution would be override the ModelAdmin instace __getattr__ method, creating the callables on the fly:
class DynamicLookupMixin(object):
'''
a mixin to add dynamic callable attributes like 'book__author' which
return a function that return the instance.book.author value
'''
def __getattr__(self, attr):
if ('__' in attr
and not attr.startswith('_')
and not attr.endswith('_boolean')
and not attr.endswith('_short_description')):
def dyn_lookup(instance):
# traverse all __ lookups
return reduce(lambda parent, child: getattr(parent, child),
attr.split('__'),
instance)
# get admin_order_field, boolean and short_description
dyn_lookup.admin_order_field = attr
dyn_lookup.boolean = getattr(self, '{}_boolean'.format(attr), False)
dyn_lookup.short_description = getattr(
self, '{}_short_description'.format(attr),
attr.replace('_', ' ').capitalize())
return dyn_lookup
# not dynamic lookup, default behaviour
return self.__getattribute__(attr)
# use examples
#admin.register(models.Person)
class PersonAdmin(admin.ModelAdmin, DynamicLookupMixin):
list_display = ['book__author', 'book__publisher__name',
'book__publisher__country']
# custom short description
book__publisher__country_short_description = 'Publisher Country'
#admin.register(models.Product)
class ProductAdmin(admin.ModelAdmin, DynamicLookupMixin):
list_display = ('name', 'category__is_new')
# to show as boolean field
category__is_new_boolean = True
As gist here
Callable especial attributes like boolean and short_description must be defined as ModelAdmin attributes, eg book__author_verbose_name = 'Author name' and category__is_new_boolean = True.
The callable admin_order_field attribute is defined automatically.
Don't forget to use the list_select_related attribute in your ModelAdmin to make Django avoid aditional queries.
if you try it in Inline, you wont succeed unless:
in your inline:
class AddInline(admin.TabularInline):
readonly_fields = ['localname',]
model = MyModel
fields = ('localname',)
in your model (MyModel):
class MyModel(models.Model):
localization = models.ForeignKey(Localizations)
def localname(self):
return self.localization.name
I may be late, but this is another way to do it. You can simply define a method in your model and access it via the list_display as below:
models.py
class Person(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
def get_book_author(self):
return self.book.author
admin.py
class PersonAdmin(admin.ModelAdmin):
list_display = ('get_book_author',)
But this and the other approaches mentioned above add two extra queries per row in your listview page. To optimize this, we can override the get_queryset to annotate the required field, then use the annotated field in our ModelAdmin method
admin.py
from django.db.models.expressions import F
#admin.register(models.Person)
class PersonAdmin(admin.ModelAdmin):
list_display = ('get_author',)
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_author = F('book__author')
)
return queryset
#admin.display(ordering='_author', description='Author')
def get_author(self, obj):
return obj._author
AlexRobbins' answer worked for me, except that the first two lines need to be in the model (perhaps this was assumed?), and should reference self:
def book_author(self):
return self.book.author
Then the admin part works nicely.
I prefer this:
class CoolAdmin(admin.ModelAdmin):
list_display = ('pk', 'submodel__field')
#staticmethod
def submodel__field(obj):
return obj.submodel.field

Categories

Resources