Model with autoincremental non PK key - django

I'm writing a model where I use an auto incremental key, which is based on two foreign keys and is not the pk.
class Message(models.Model):
message_id = models.IntegerField()
user_1 = models.ForeignKey(User)
user_2 = models.ForeignKey(User)
class Meta:
unique_together = ("message_id", "user_1", "user_2")
As far as I know, an AutoField can't be used for this case.
What is the best way to achieve this. (It might be the case, that two new messages are created at the same time).

Django doesn't support composite primary keys, yet, so best way is pretty much the way you're doing it now.
Keep the automatic id column that Django generates and then add a unique index for the columns that actually are the primary key, the unique index will then take care of ensuring that there's no duplicates.

Related

Using `select_related` with a table with composite primary key [Django]

Is there a way for Django to support composite primary key in combination of select_related or prefetch_related?
I have a Records table with a composite primary key (device_id, created) with a schema like this (simplified):
class Records(models.Model):
class Meta:
managed = False
unique_together = (("device", "created"),)
# The primary key is really (device_id, created)
device = models.ForeignKey("hardware.Device", primary_key=True, on_delete=models.CASCADE)
created = models.DateTimeField(db_index=True)
value = models.FloatField(default=0)
A record belongs to a Device, which is modeled like this (simplified):
class Device(models.Model):
car = models.ForeignKey("cars.Car", on_delete=models.CASCADE)
last_record_at = models.DateTimeField(null=True, blank=True)
I wish to run a query that will return a list of devices, but for each one also
contain the last record.
In theory, this would be something like that:
Device.objects.filter(...).select_related("car", "last_record")
But obviously "last_record" is not a foreign key, since Records contains a composite primary key which Django doesn't support.
What would be the best way to do this, other than rewriting the query in raw sql? Is there a reasonable way to override select_related to handle composite keys?

Django GenericForeignKey vs set of ForeignKey

I would like to discuss the case of using GenericRelation and GenericForeignKey.
I have the 2 models, Appartement and Mission. And I need to create LockCode model which will be connected to Appartment or Mission. I have added the GenericForeignKey to the LockCode and GenericRelation to the Appartement and Mission:
class LockCode(TimeStampedModel):
context_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
context_id = models.PositiveIntegerField()
context = GenericForeignKey('context_type', 'context_id')
class Mission(DirtyFieldsMixin, models.Model):
lock_codes = GenericRelation(
LockCode,
content_type_field='context_type',
object_id_field='context_id',
related_query_name='mission'
)
class Appartment(DirtyFieldsMixin, models.Model):
lock_codes = GenericRelation(
LockCode,
content_type_field='context_type',
object_id_field='context_id',
related_query_name='appartment'
)
It works ok. But added the complexity level to compare with adding 2 ForeignKeys, for appartement and for mission.
class LockCode(TimeStampedModel):
appartement = models.ForeignKey(Appartement, null=True, blank=True)
mission = models.ForeignKey(Mission, null=True, blank=True)
So should I keep the GFK or use 2 simple FK?
If you go with the second option, you will have to build in logic to make sure either appartement or mission is not null. If you were to ever add more such foreign key fields, this logic would become increasingly complex.
If you are sure you are never going to add more foreign keys and you don't mind the overhead of ensuring one of them is not null then you can go ahead and use the foreign keys, but for scalability I would stick with generic relations.
The complexity does not all happen in the field definitions. It also happens at query time: given a LockCode, how do you identify whether it belongs to an apartment or mission? With two foreign keys, you would need to check both each time and catch any exceptions.
If you never need to follow the relationship that way, then yes the GFK is unnecessary and two FKs would be better.

Django Composite Foreign Key

Is there a way to create a composite foreign in Django and use the key to create and update certain entries?
I have looked at the package django-composite-foreignkey. However, it doesn't provide a way to use the key for creating/updating
As an example:
I have
class tree(models.Model):
id = models.AutoField(primary_key=True)
tree_type = models.CharField(max_length=255)
tree_age = models.IntegerField()
class branch(models.Model):
tree_id = models.ForeignKey('tree',on_delete=models.CASCADE)
branch_id = models.IntegerField()
branch_length = models.FloatField()
Branch_width = models.FloatField()
class Meta:
unique_together(('tree_id','branch_id'),)
class leaf(models.Model):
tree_id = models.ForeignKey('tree',on_delete=models.CASCADE)
branch_id = models.ForeignKey('branch',on_delete=models.CASCADE)
leaf_id = models.IntegerField()
leaf_color = models.CharField(max_length=255)
leaf_length = models.FloatField()
leaf_width = models.FloatField()
worm_bites = models.IntegerField()
class Meta:
unique_together(('tree_id','branch_id','leaf_id')
And I want to create a new leaf say
#not working
leaf.objects.create(tree_id = 2, branch_id = 2, leaf_id = 1, leaf_color = 'brown'.....)
This doesn't work, as it will give me a ForeignKey error. I guess is because the branch_id is not a primary key in BRANCH.
So I am wondering if there is a way to use composite Primary Key / composite Foreign Key in Django?
Thanks!
When creating an object that has a foreign key, you have to pass an object (not just an id) that has been saved to the database previously. For instance:
branch = Branch.objects.get(id=1)
tree = branch.tree
Leaf.objects.create(tree=tree, branch=branch ...)
A few tipps on naming conventions: Classes should be camel cased: Branch, BigBranch and Leaf. Models should be singular. Foreign keys should be named after the model if possible, but without a trailing _id. The Django code actually is an excellent example of Python naming conventions.
As to the composite key business, you don't need composite foreign keys if you design your database accordingly. In this particular case, every item in the hierarchy should only point to the next-higher level. Imagine if you had a Forest. What would you do, add a third foreign key to Leaf? No need: Leaf points to Branch, Branch to Tree, and Tree to Forest, and you can always go through that chain to find which forest a leaf is in without saving a reference to the forest in Leaf.
The Django documentation (which is excellent in many ways) has very useful examples on how to define relationships.

Django - Remove Unique Constraint from a field

I have a model like
class LoginAttempts(models.Model):
user = models.OneToOneField(User, unique=False)
counter = models.IntegerField(null=True)
login_timestamp = models.DateTimeField(auto_now=True)
The table created in db is like
However If I create another entry with user_id = 362 it fails with IntegrityError: duplicate key value violates unique constraint. Id is already my primary key, I want to have same user having different counters instead of creating a new table referencing them since this is simple table.
How to achieve the same or what what could be the best way. I want to restrict user to some specified number of failed logins.
If you want a relationship that permits more than one LoginAttempt for a User, you should not use OneToOneField. by definition, that implies only one item on each side. Instead, use ForeignKey.
The very nature of a OneToOneField is that it's a ForeignKey with a unique constraint.
However, if you don't want separate entries, then update the counter and login_timestamp fields:
from django.utils import timezone
def update_attempts_for_user(user):
attempts, created = LoginAttempts.objects.get_or_create(user=user, defaults={'counter': 1, 'login_timestamp': timezone.now())
if not created:
attempts.counter += 1
attempts.login_timestamp = timezone.now()
attempts.save(update_fields=['counter', 'login_timestamp'])

django models: how to overcome 'through' ManyToMany option limitation

I'm working on a app for allowing users to create and manage user groups by themselfs.
The problem is I want to store which user added a new member to any group.
These are my models at the moment:
class UserManagedGroup(Group):
leader = models.ForeignKey(User, verbose_name=_('group leader'), related_name='leaded_groups')
members = models.ManyToManyField(User, verbose_name=_('members'), through='Membership',
related_name='managed_groups')
class Membership(models.Model):
user = models.ForeignKey(User, related_name='memberships')
group = models.ForeignKey(UserManagedGroup, related_name='memberships')
info = models.OneToOneField('MembershipInfo', verbose_name=_('membership information'))
class Meta:
unique_together = ['user', 'group']
class MembershipInfo(models.Model):
date_added = models.DateField(_('date added'), auto_now_add=True)
made_member_by = models.ForeignKey(User, verbose_name=_('user who made him a member'))
membership_justification = models.TextField(_('membership justification'), blank=True, default='')
#receiver(signals.post_delete, sender=Membership)
def delete_membership_info(sender, instance, *args, **kwargs):
if instance.info.pk:
instance.info.delete()
As you can see, I have a silly MembershipInfo model which would fit much better merged with Membership because of the nature of its fields. Also, MembershipInfos life is bound to its Membership (which is why I had to create this post_delete signal connection).
I can't merge them because of this:
Your intermediate model must contain one - and only one - foreign key to the target model (this would be Person in our example). If you have more than one foreign key, a validation error will be raised.
(In my case I can't use 2 foreign keys to User)
Now, this actually works but, I don't like it. It makes Membership instance creation tedious since I must always create a MembershipInfo instance first. Also, 2 queries instead of 1.
QUESTION Best way of storing 2 foreign keys to the same model (User) bound to my member relationship.
I just worked through a similar problem which included an intermediate model with two foreign keys to the same target. This is what my system looks like:
class Node(models.Model):
receivers = models.ManyToManyField('self', through='Connection', related_name='senders', symmetrical=False)
class Connection(models.Model):
sender = models.ForeignKey(Node, related_name='outgoing')
receiver = models.ForeignKey(Node, related_name='incoming')
I think this illustrates the main requirements for using two foreign keys to the same target in an intermediate model. That is, the model should have a ManyToManyField with the target 'self' (recursive ManyToMany) and the attribute through pointing to the intermediate model. I think it's also necessary that each foreign key be assigned a unique related_name. The symmetrical=False argument applies to recursive relationships if you want them to be one-way, e.g. Node1 sends signals to Node2, but Node2 doesn't necessarily send signals to Node1. It is necessary that the relationship be defined with symmetrical=False in order for a recursive ManyToMany to use a custom 'through' model. If you want to create a symmetrical recursive ManyToMany with a custom 'through' model, advice can be found here.
I found all these interrelationships fairly confusing, so it took me awhile to choose sensible model attributes and related_names that actually capture what the code is doing. To clarify how this works, if I have a node object N, calling N.receivers.all() or N.senders.all() return sets of other Nodes that receive data from N or send data to N, respectively. Calling N.outgoing.all() or N.incoming.all() access the Connection objects themselves, through the related_names. Note that there is still some ambiguity in that senders and receivers could be swapped in the ManyToManyField and the code would work equally well, but the direction becomes reversed. I arrived at the above by checking a test case for whether the 'senders' were actually sending to the 'receivers' or vice versa.
In your case, targeting both foreign keys to User adds a complication since it's not obvious how to add a recursive ManyToManyField to User directly. I think the preferred way to customize the User model is to extend it through a proxy that's connected to User through a OneToOneField. This is maybe unsatisfying in the same way that extending Membership with MembershipInfo is unsatisfying, but it does at least allow you to easily add further customization to the User model.
So for your system, I would try something like this (untested):
class Member(models.Model):
user = models.OneToOneField(User, related_name='member')
recruiters = models.ManyToManyField('self', through = 'Membership', related_name = 'recruits', symmetrical=False)
other_custom_info = ...
class UserManagedGroup(Group):
leader = models.ForeignKey(Member, related_name='leaded_groups')
members = models.ManyToManyField(Member, through='Membership', related_name='managed_groups')
class Membership(models.Model):
member = models.ForeignKey(Member, related_name='memberships')
made_member_by = models.ForeignKey(Member, related_name='recruitments')
group = models.ForeignKey(UserManagedGroup, related_name='memberships')
date_added = ...
membership_justification = ...
The recursive field should be asymmetrical since Member1 recruiting Member2 should not also mean that Member2 recruited Member1. I changed a few of the attributes to more clearly convey the relationships. You can use the proxy Member wherever you would otherwise use User, since you can always access Member.user if you need to get to the user object. If this works as intended, you should be able to do the following with a given Member M:
M.recruiters.all() -> set of other members that have recruited M to groups
M.recruits.all() -> set of other members that M has recruited to groups
M.leaded_groups.all() -> set of groups M leads
M.managed_groups.all() -> set of groups of which M is a member
M.memberships.all() -> set of Membership objects in which M has been recruited
M.recruitments.all() -> set of Membership objects in which M has recruited someone
And for a group G,
G.memberships.all() -> set of Memberships associated with the group
I think this should work and provide a 'cleaner' solution than the separate MembershipInfo model, but it might require some tweaking, for example checking the direction of the recursive field to make sure that recruiters are recruiting recruits and not vice-versa.
Edit: I forgot to link the Member model to the User model. That would be done like this:
def create_member(member, instance, created, **kwargs):
if created:
member, created = Member.objects.get_or_create(user=instance)
post_save.connect(create_member, member=User)
Note that create_member is not a method of Member but is called after Member is defined. By doing this, a Member object should be automatically created whenever a User is created (you may need to set the member fields to null=True and/or blank=True if you want to add users without initializing the Member fields).
The simpliest way that I see is to remove the ManyToMany field from your UserManagedGroup and to merge Membership and MembershipInfo.
You will able to access your members as well with the entry_set fields.