Django: How to follow ForeignKey in unique_together constraint? - python

I have three Django Models:
a Property Model, most importantly having a slug field;
a Collection Model; and
a PropertyCollectionRelationship Model.
The last model models a ManyToMany relationship between the other two and is used as an explicit through of a ManyToManyField.
from django.db import models
class Property(models.Model):
slug = models.SlugField()
collections = models.ManyToManyField('Collection', blank=True, through='PropertyCollectionRelationship')
# ... other unimportant stuff omitted ...
class Collection(models.Model):
pass
# ... lots of stuff omitted ...
class PropertyCollectionRelationship(models.Model):
prop = models.ForeignKey(Property, on_delete=models.CASCADE)
coll = models.ForeignKey(Collection, on_delete=models.CASCADE)
I would like to add a uniqueness constraint which ensures that each collection has at most one property with any given slug. I tried to express this via a unique_together option in the Model Meta:
class PropertyCollectionRelationship(models.Model):
class Meta:
unique_together = [['coll', 'prop__slug']]
prop = models.ForeignKey(Property, on_delete=models.CASCADE)
coll = models.ForeignKey(Collection, on_delete=models.CASCADE)
This resulted in the following System Check Error:
SystemCheckError: System check identified some issues:
ERRORS:
myapp.PropertyCollectionRelationship: (models.E012) 'unique_together' refers to the nonexistent field 'prop__slug'.
How, if at all, can I achieve such a constraint?
Edit: As suggested below I could set unique=True on the slug field, however my use case requires different properties with the same slug to coexist.

Related

How to change the behaviour of unique true in django model?

Here I am not deleting the model objects from database. I am just changing the is_deleted status to True while delete. But while doing this unique=True gives error for the deleted objects so how can I handle this ?
I want to exclude is_deleted=True objects from unique True.
class MyModel(models.Model):
name = models.CharField(max_length=20, unique=True)
is_deleted = models.BooleanField(default=False)
#views
class MyViewSet(ModelViewSet):
serializer_class = MySerializer
queryset = MyModel.objects.all()
def destroy(self, request, *args, **kwargs):
object = self.get_object()
object.is_deleted = True
object.save()
return Response(status=status.HTTP_204_NO_CONTENT)
You can use a UniqueConstraint [Django docs] with a condition instead of the unique kwarg on the field. Although there is a caveat that validation (by forms etc.) will not be done automatically for a unique constraint with a condition and you will need to do that yourself.
from django.db.models import Q
class MyModel(models.Model):
name = models.CharField(max_length=20)
is_deleted = models.BooleanField(default=False)
class Meta:
constraints = [
models.UniqueConstraint(fields=['name'], condition=Q(is_deleted=False), name='unique_undeleted_name')
]
Note: Since Django 4.1 validation is automatically performed for all constraints using the Model.validate_constraints method and hence the above mentioned caveat doesn't apply.
Since django-2.2, you can work with Django's constraint API, you can then specify a UniqueConstraint [Django-doc] that has a condition:
class MyModel(models.Model):
# no unique=True ↓
name = models.CharField(max_length=20)
is_deleted = models.BooleanField(default=False)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['name'],
name='unique_name_not_deleted',
condition=Q(is_deleted=False)
)
]
It is of course the database that enforces this, and thus some databases might not have implemented that feature.
You may use following also:
class Meta:
unique_together = ("name", "is_deleted")

Having trouble wrapping my head around follower/target models in Models.py

I have just started with making a similar site to Pinterest and the site has follower/target system that I have barely any understanding of. So far, my models.py code is below:
from django.db import models
class User(models.Model):
username = models.CharField(max_length=45, null=True)
email = models.CharField(max_length=200, null=True)
password = models.CharField(max_length=200)
nickname = models.CharField(max_length=45, null=True)
target = models.ManyToManyField(self, through='Follow')
follower = models.ManyToManyField(self, through='Follow')
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='targets')
target = models.ForeignKey(User, on_delete=models.CASCADE, related_name='followers')
class Meta:
db_table = 'follows'
This code was made with reference to another StackOverflow thread
Django models: database design for user and follower
However, I am having trouble understanding how using "related_name='targets' in 'follower' and "related_name='followers'" in 'target' where I can't see any 'targets'(plural) or 'followers'(plural) in other areas of models.py
Should I get rid of that related_name, since there is no such table called "followers" or "targets"? And if you spot major errors in my code or logic, can you tell me? Thanks!
Should I get rid of that related_name, since there is no such table called followers or targets.
There is never a table named followers or targets. The related_name [Django-doc] is a conceptual relation Django makes to the other model (in this case User). It means that for a User object myuser, you can access the Follow objects that refer to that user through target for example with myuser.followers.all(), so:
Follow.objects.filter(target=myuser)
is equivalent to:
myuser.followers.all()
The default of a related_name is modelname_set, so here that would be follow_set. But if you remove both related_names, then that would result in a name conflict, since one can not add two relations follow_set to the User model (and each having a different semantical value).
if you spot major errors in my code or logic, can you tell me?
The problem is that since ManyToManyFields refer to 'self' (it should be 'self' as string literal), it is ambigous what the "source" and what the target will be, furthermore Django will assume that the relation is symmetrical [Django-doc], which is not the case. You should specify what the source and target foreign keys are, you can do that with the through_fields=… parameter [Django-doc]. It furthermore is better to simply define the related_name of the ManyToManyField in reverse, to avoid duplicated logic.
from django.db import models
class User(models.Model):
username = models.CharField(max_length=45, unique=True)
email = models.CharField(max_length=200)
password = models.CharField(max_length=200)
nickname = models.CharField(max_length=45)
follows = models.ManyToManyField(
'self',
through='Follow',
symmetrical=False,
related_name='followed_by',
through_fields=('follower', 'target')
)
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='targets'
)
target = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='followers'
)
class Meta:
db_table = 'follows'
Here a User object myuser can thus access myuser.follows.all() to access all the users that they follow, myuser.followed_by.all() is the set of Users that follow myuser. myuser.targets.all() is the set of Follow objects that he is following, and myuser.followers.all() is the set of Follow objects that are following that user.

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? )

How can I use the automatically created implicit through model class in Django in a ForeignKey field?

In Django I have the following models.
In the Supervisor model I have a many-to-many field without an explicitly defined through table. In the ForeignKey field of the Topic model I would like to refer to the automatically created intermediate model (created by the many-to-many field in the Supervisor model), but I don't know what is the name of the intermediate model (therefore I wrote '???' there, instead of the name).
Django documentation tells that "If you don’t specify an explicit through model, there is still an implicit through model class you can use to directly access the table created to hold the association."
How can I use the automatically created implicit through model class in Django in a ForeignKey field?
import re
from django.db import models
class TopicGroup(models.Model):
title = models.CharField(max_length=500, unique='True')
def __unicode__(self):
return re.sub(r'^(.{75}).*$', '\g<1>...', self.title)
class Meta:
ordering = ['title']
class Supervisor(models.Model):
name = models.CharField(max_length=100)
neptun_code = models.CharField(max_length=6)
max_student = models.IntegerField()
topicgroups = models.ManyToManyField(TopicGroup, blank=True, null=True)
def __unicode__(self):
return u'%s (%s)' % (self.name, self.neptun_code)
class Meta:
ordering = ['name']
unique_together = ('name', 'neptun_code')
class Topic(models.Model):
title = models.CharField(max_length=500, unique='True')
foreign_lang_requirements = models.CharField(max_length=500, blank=True)
note = models.CharField(max_length=500, blank=True)
supervisor_topicgroup = models.ForeignKey(???, blank=True, null=True)
def __unicode__(self):
return u'%s --- %s' % (self.supervisor_topicgroup, re.sub(r'^(.{75}).*$', '\g<1>...', self.title))
class Meta:
ordering = ['supervisor_topicgroup', 'title']
It's just called through - so in your case, Supervisor.topicgroups.through.
Although I think that if you're going to be referring to it explicitly in your Topic model, you might as well declare it directly as a model.

Django ManyToMany Inlines Ordering in 1.2.x

I'm using Django 1.2's new ManyToMany admin.TabularInline to display related objects in the admin app, and it works great except I can't figure out what to set the "ordering" property to so it can sort by one of the cross-referenced field names.
For instance:
class Foo(models.Model):
name = models.CharField(max_length=100)
class Bar(models.Model):
title = models.CharField(max_length=100)
foos = models.ManyToManyField(Foo)
class FooBarInline(admin.TabularInline):
model = Bar.foos.through
ordering = ('name', ) # DOES NOT WORK
raw_id_fields = ('name', ) # THROWS EXCEPTION
class FooAdmin(admin.ModelAdmin):
inlines = (FooBarInline, )
class Meta:
model = Foo
How can I get to the Foo.name field to order by it in the inline?
The model ordering meta option designates the order of the inline elements.
class Foo(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name',)
If you needed to have the ordering of the admin model different from your primary ordering, you could do something like this:
class Foo_Extended(Foo):
class Meta:
ordering = ('name',)
And use Foo_Extended for your AdminInline model.
I'm assuming you know this, but Django 1.3 adds and ordering option to the InlineAdmin model but I know you said Django 1.2
I think you may override
ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)
You can find details in the docs for ModelAdmin.formfield_for_foreignkey.

Categories

Resources