Combining abstract model class and multi-table inheritance in Django - python

I have three model classes:
django.contrib.auth.models.User, reffered to as User
mysite.models.Profile, reffered to as Profile
mysite.models.Subscriber, reffered to as Subscriber
Profile inherits from User in a way that is well described in docs as a solution to add custom properties to User model without bothering with swappable models (which were only added in version 1.5).
While Profile and Subscriber are different objects, they do share some properties. Namely, I want to use custom primary key algorithm with both and override save() method in a similar way, so that code can be reused in accordance with DRY. Now, if both were plain model classes, that would be simple:
class BaseProfile(models.Model):
key = models.PositiveIntegerField(primary_key=True)
activated = models.BooleanField(default=False)
...
class Meta:
abstract = True
def save():
...
class Profile(BaseProfile):
...
class Subscriber(BaseProfile):
...
However, Profile already uses multi-table inheritance. I'm thinking of a way similar to this:
class BaseProfile(models.Model):
key = models.PositiveIntegerField(primary_key=True)
activated = models.BooleanField(default=False)
...
class Meta:
abstract = True
def save():
...
class Profile(BaseProfile, User):
user = models.OneToOneField(User, parent_link=True, blank=True, null=True, on_delete=models.CASCADE)
...
class Subscriber(BaseProfile):
...
Would that be possible? If so, what order of inheritance is needed in my case, so that both model fields and save() method are called in a correct way? Will Meta of both model class not get in conflict?

The documentation you link to does not describe inheriting from User via multi-table inheritance. It does explain that you can link a 'profile' like object by using a OneToOneField. Try:
class Profile(BaseProfile):
user = models.OneToOneField(User, blank=True, null=True, on_delete=models.CASCADE)
...
I suspect you don't actually want blank=True and null=True in there, however.
This approach does mean that your User objects will very likely not have the same primary keys as their corresponding Profile objects but this might well be OK for you.

Related

How to make my models follow DRY principles

I have a model in which I need to represent different jobs for a labor application, for example:
from django.db import models
class PostFirstJobAd(models.Model):
fist_job_ad_title = models.CharField(max_length=225)
first_job_ad_description = models.TextField()
created_at = models.DateTimeField(auto_now=True)
class PostSecondJobAd(models.Model):
second_job_ad_title = models.CharField(max_length=225)
second_job_ad_description = models.TextField()
created_at = models.DateTimeField(auto_now=True)
class PostThirdJobAd(models.Model):
third_job_ad_title = models.CharField(max_length=225)
third_job_ad_description = models.TextField()
created_at = models.DateTimeField(auto_now=True)
Instantly you can see that I'm repeating title, description and created_at, I'm just changing field's name, it's not DRY and code is starting to smell. The reason for this is because I want to register every job separately inside django admin, so I will have clear situation inside site administration.
One way to make them DRY is to use Abstract base classes but I have a problem because from the docs:
This model will then not be used to create any database table.
Instead, when it is used as a base class for other models, its fields
will be added to those of the child class.
What will be the right approach in this case, can someone help me understand this.
Using abstract base models:
class JobAd(models.Model):
title = models.CharField(max_length=225)
description = models.TextField()
created_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class PostFirstJobAd(JobAd):
pass
class PostSecondJobAd(JobAd):
pass
class PostThirdJobAd(JobAd):
pass
This would create 3 tables. The base class JobAd does not have a table in the db.
Since you appear to have 3 different models with the exact same code, you should question whether you really need 3 different models at all. Another option is to just store them all in one table, and add another field for the "other" thing.
class JobAd(models.Model):
pos = models.CharField(max_length=100, choices=['first', 'second', 'third'])
title = models.CharField(max_length=225)
description = models.TextField()
created_at = models.DateTimeField(auto_now=True)
An integer field for pos is also possible.
First off, the abstract models might be what you need here. Depending on the business requirements, you may need to think a little harder on the architecture.
If, in fact, you do need to use abstract base classes:
class BaseJob(models.Model):
title = models.CharField(max_length=255)
# etc...
class Meta:
abstract = True
def method_1(self):
# base methods that work for instance data
Once that is defined, you can implement the base class in a concrete model. A concrete model is a model that doesn't use the abstract = True metaclass property (or proxy, etc.) like so:
class Job(BaseJob):
pass
If you need additional fields you can define them like any other model field but when you run makemigrations you'll find the fields get added in the migration generated.

Creating many to many relation with AUTH_USER_MODEL in django via intermediary model

I am trying to create the following models. There is a ManyToMany relation from Entry to AUTH_USER_MODEL via the EntryLike intermediate model.
class BaseType(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
creation_time = models.DateTimeField(auto_now_add=True)
last_update_time = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Title(BaseType):
text = models.CharField(max_length=100)
description = models.TextField()
class EntryLike(BaseType):
entry = models.ForeignKey(Entry)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class Entry(BaseType):
title = models.ForeignKey(Title, on_delete=models.PROTECT)
text = models.TextField()
user = models.ForeignKey(settings.AUTH_USER_MODEL)
liked_by_users = models.ManyToManyField(settings.AUTH_USER_MODEL, through='EntryLike', through_fields=('entry', 'user'))
Running migrations on the above model scheme throws the error: AttributeError:'str' object has no attribute 'meta'.
Any help in resolving this error would be highly appreciated. Am new to Django & Python, but not to Web Development.
The issue is that settings.AUTH_USER_MODEL is almost certainly not a model instance. It's probably a string that constrains the choices another model can make - settings would be a strange place to leave a model definition.
To do a MTM between the user model and your field above you need need to do:
from django.contrib.auth.models import User
class Entry(BaseType):
title = models.ForeignKey(Title, on_delete=models.PROTECT)
text = models.TextField()
user = models.ForeignKey(User)
def __str__(self):
return self.title
I've added the str function so that it gives a more sensible return when you're manipulating it in admin/shell.
I'd also question whether you need the second set of fields (removed here), as you can use select related between the Entry and EntryLike join table, without any duplication of the fields - you can probably go that way, it's just a bit unnecessary.
Lastly, I'd note that the way I'm using it above just uses the default User object that comes with Django - you may wish to customise it. or extend the base class as you've done here with your own models' base class.
(All of this is predicated on AUTH_USER_MODEL not being a model instance - if it is, can you post the model definition from settings.py? )

Correctly using subclasses/proxies in django that have self referential foreign keys?

I have two classes, with a super class. In essence the two classes are concrete classes on a tree. One is a leaf, one is a branch. They share properties defined in the super class.
None of the below classes are finished. I've tried both making the superclass abstract, and the subclasses proxies. Hopefully the code below explains what I'm trying to achieve.
This is the 'super class'
class Owner(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
class Meta:
abstract=True
This is the 'leaf'
class User(Owner):
pass
This is the 'branch'.
class Group(Owner):
head = models.ForeignKey(User)
members = models.ManyToManyField(Owner,through='Membership')
This shows how a user can belong to a group by a membership.
class Membership(models.Model):
date_joined = models.DateField()
user = models.ForeignKey(Owner)
group = models.ForeignKey(Group)
My restrictions are that each user can belong to many groups (via the linker Membership). Each group can be a member of a single group.
This fails because I'm referencing Owner in both the membership as the user, and in the group members. I feel like this is the sort of thing I could solve with generics in Java, but thats not going to help me here.
I've also seen ContentTypes used for this sort of thing, but they seem too complicated for what I'm trying to do. Am I wrong? I can't figure out how to apply that paradigm to my example. I also found this question but I'm still not certain on how it should be implemented.
You can't have foreign key fields pointing to an abstract class (there is no table in the DB for an abstract class).
You'll probably need to implement self-referential foreign key for each Group to belong to zero or one group. Something like this:
class Base(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
class Meta:
abstract=True
class User(Base):
groups = models.ManyToManyField('Group', through='Membership', related_name='members')
class Group(Base):
head = models.ForeignKey(User)
parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
def descendants(self, **kwargs):
qs = self.children_set.filter(**kwargs)
for group in self.children_set.all():
qs = qs | group.descendants(**kwargs)
return qs
class Membership(models.Model):
date_joined = models.DateField()
user = models.ForeignKey(User)
group = models.ForeignKey(Group)
The Base class above does nothing other than dictate that each inherited class has a name field in their respective DB table and __unicode__ method- nothing more. You could simply copy and paste the name field and __unicode__ method into User and Group and it would be functionally identical. Having a common abstract parent doesn't create any DB relationships and you can't use it to query the DB.
I can't really see any reason for using proxy classes in this situation.

Django override ManyRelatedManager

I've a standard User, Friendship model in my Django application.
In the User model I have defined the "friends" ManyToMany field with self relation through Friendship model.
class User(AbstractBaseUser, PermissionsMixin):
...
friends = models.ManyToManyField("self", through="Friendship",
symmetrical=False, blank=True, verbose_name="Friends")
class Friendship(models.Model):
friendship_date = models.DateTimeField(auto_now_add=True, editable=False)
from_user = models.ForeignKey(User, related_name="+")
to_user = models.ForeignKey(User, related_name="+")
If I implement the symmetric relation, with manually call twice the save() method of Friendship, the code: myUserObject.friends.all() return all friends.
But, if don't implements the symmetric relation and user1 and user2 are friends, only user1.friends.all() return me user2..... user2.friends.all() doesn't return anything.
Since user1.friends is ManyRelatedManager, I need to override this manager to reimplement the search function logic (ex: with Q object for search in from_user and to_user field).
The solution to add get_friends(self) to my Model doesn't seems ok...
Can I use #property decorator for friends field?
Any suggestions?

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(...)

Categories

Resources