I have a model Run with two ForeignKeys, signup and report,
class Run(models.Model):
signup = models.ForeignKey(Signup, on_delete=models.CASCADE, related_name="runs")
report = models.ForeignKey(Report, on_delete=models.CASCADE, related_name="runs")
kind = ...
pointing to models, which in turn are related to another model, Training,
class Report(models.Model):
training = models.OneToOneField(
Training, on_delete=models.CASCADE, primary_key=True
)
cash_at_start = ....
class Signup(models.Model):
training = models.ForeignKey(
Training, on_delete=models.CASCADE, related_name="signups"
)
participant = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="signups"
)
When creating a Run I would like to make sure, that it's signup and report are for the same Training, i.e. that report.training == signup.training.
What is this kind of validation called? And how would I achieve it?
Also, I am happy to learn other ways to implement this, if another database structure would be better.
Here are the docs that describe the validation process on models.
Please note that this validation usually only happens with ModelForms (when calling form.is_valid()). Manually creating an object and using save() doesn't trigger this validation if you don't call model_instance.full_clean() method. It's aimed at the user, not the developer.
According to the mentioned docs, my suggestion is to use the clean method:
class Run(models.Model):
signup = models.ForeignKey(Signup, on_delete=models.CASCADE, related_name="runs")
report = models.ForeignKey(Report, on_delete=models.CASCADE, related_name="runs")
def clean(self):
if self.signup.training != self.report.training:
# Note: adding an error code is best practice :)
raise ValidationError('some message', code='some_error_code')
EDIT #1: Not using ModelForm
You don't have to use the ModelForm to have validation on the model. Let's say you obtained Signup and Report instances from somewhere. Then you can instantiate and validate your Run instance like this:
run = Run(signup=signup, report=report)
# Raises ValidationErrors
run.full_clean()
# If all is fine, call save
run.save()
Related
I'm trying to build web app with Django on my backend and I want to make some type of people recommendations and show only people the current user doesn't follow, however I don't know how to do this.
It seems like there are questions on StackOverflow however they are about SQL queries and I still only know the Django Models for the database.
I use the standard Django User Model and the following model for the following relationship
class Follow(models.Model):
# This means following(Person A) follows follower(Person B)
following = models.ForeignKey(User, on_delete=models.CASCADE, related_name='following')
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='follower')
def __str__(self):
return str(self.following.username) + " follows " + str(self.follower.username)
class Meta:
unique_together = (('following', 'follower'), )
I use the following query to get all the users the current user follows, but I would like to sort-of invert it.
Follow.objects.filter(following=user.id).prefetch_related('following')
Use exclude insteed of filter
Follow.objects.exclude(following=user.id).prefetch_related('following')
I am trying to build a system which will enable my users to create their own reports based on the model of their choosing without me having to code them every time they need updating.
In order to do this, I've come up with the following models:-
from django.db import models
from django.contrib.contenttypes.models import ContentType
class ReportField(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
data_method = models.CharField(max_length=100)
def get_value_for(self, object):
return getattr(object, self.data_method)
class Report(models.Model):
name = models.CharField(max_length=200)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
data_fields = models.ManyToManyField(ReportField)
The idea is that users can create a Report based on the model they're interested in. They can then add any number of ReportFields to that report and, when the report runs, it will call the data_method (the name of a property) on each instance of the model in the db.
The bit I'm having trouble with is defining which properties the users can have access to. I need to have a way of creating a load of ReportFields with certain data_methods for each model. But I don't want to create them by hand - I want it to work in a similar way to the way Permissions work in Django, if that's possible, like this:-
class MyModel(models.Model):
class Meta:
data_methods = (
('property_name_1', 'Property Name 1'),
('property_name_2', 'Property Name 2'),
etc.
)
From reading the source code, Django seems to run a management command after every migration on that model to make sure the model permissions are created. Is that the only way to do this? Am I going in the right direction here, or is there a better way?
I am having trouble deciding how to structure my models for a particular data structure.
The models I have would be Posts, Groups, Users.
I want the Post model that can be posted from a groups page or user page and potentially more, like an events page.
Posts would contain fields for text, images(fk), user, view count, rating score (from -- a reference to where ever it was posted from like user or group page, though I am unsure how to make this connection yet)
I thought about using a Generic Foreign Key to assign a field to different models but read articles suggesting to avoid it. I tried the suggested models, but I wasn't unsure if they were the right approach for what I required.
At the moment I went with Alternative 4 - multi-table inheritance
class Group(models.Model):
name = models.CharField(max_length=64)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='_groups')
members = models.ManyToManyField(
settings.AUTH_USER_MODEL)
def __str__(self):
return f'{self.name} -- {self.created_by}'
def save(self, *args, **kwargs):
# https://stackoverflow.com/a/35647389/1294405
created = self._state.adding
super(Group, self).save(*args, **kwargs)
if created:
if not self.members.filter(pk=self.created_by.pk).exists():
self.members.add(self.created_by)
class Post(models.Model):
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_posts",
related_query_name="%(app_label)s_%(class)ss")
# class Meta:
# abstract = True
def __str__(self):
return f'{self.content} -- {self.created_by}'
class PostImage(models.Model):
image = models.ImageField(upload_to=unique_upload)
post = models.ForeignKey(
Post, related_name='images', on_delete=models.CASCADE)
def __str__(self):
return '{}'.format(self.image.name)
class UserPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_uPost', on_delete=models.CASCADE)
class GroupPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_gPost', on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
To do some specific filters ex:
Filter specific group post
Post.objects.filter(_gPost__group=group)
Filter specific user post
Post.objects.filter(created_by=user) # exclude groups with _gPost__isnull=False
Create post to user/group
p = Post.objects.create(...)
up = UserPost.objects.create(post=p)
gp = GroupPost.objects.create(post=p)
Really I am wondering if this is a sensible approach. The current way of a filter and creating feel odd. So only thing making me hesitant on this approach is just how it looks.
So, is Generic ForeignKey the place to use here or the current multi-table approach. I tried going with inheritance with abstract = True and that was unable to work as I need a foreign key to base post model. Even with no abstract, I got the foreign key reference, but filter became frustrating.
Edit:
So far only weird issues(but not really) are when filtering I have to be explicit to exclude some field to get what I want, using only .filter(created_by=...) only would get all other intermediate tables.
Filter post excluding all other tablets would requirePost.objects.filter(_uPost__isnull=True, _gPost__isnull=True, _**__isnull=True) which could end up being tedious.
I think your approach is sensible and that's probably how I would structure it.
Another approach would be to move the Group and Event foreignkeys into the Post model and let them be NULL/None if the Post wasn't posted to a group or event. That improves performance a bit and makes the filters a bit more sensible, but I would avoid that approach if you think Posts can be added to many other models in the future (as you'd have to keep adding more and more foreignkeys).
At the moment I will stick with my current pattern.
Some extra reading for anyone interested.
https://www.slideshare.net/billkarwin/sql-antipatterns-strike-back/32-Polymorphic_Associations_Of_course_some
I have several models that have a ForeignKey back to a model which has a ForeignKey back to auth User in Django.
models.py
class UserDetails(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name='userdetail_related')
details = models.CharField(max_length=100)
email = models.EmailField(unique=True)
class UserInformation(models.Model):
user = models.ForeignKey(
UserDetails,
related_name='userinfo_related')
info = models.CharField(max_length=100, default='this')
EDIT
My actual code for related_name as per Django Documentation is: related_name='%(app_label)s_%(class)s_related'. I put 'userdetail_related' for ease of explanation here.
Only one UserDetail per User, but many UserInformation per UserDetail.
Where there is an unregistered user and we have captured their email, the email can have UserDetail and UserInformation associated with it for a shopping cart guest checkout system.
In my View I want to access the UserInformation model from self.request.user.
I can access UserDetails in my view via:
details = self.request.user.userdetail_related.filter(
user=self.request.user).first()
But I can't seem to access UserInformation via:
info = self.request.user.userdetail_related.filter(
user=self.request.user).first().userinfo_related.filter(
info='this').first()
The only way I can get this to work is:
details = self.request.user.userdetail_related.filter(
user=self.request.user).first()
info = details.userinfo_related.filter(
info='this').first()
But this surely hits the database twice which I don't want.
Does anyone have a better way of getting the info from UserInformation using the session user 'through' UserDetails?
You can use following:
user_info = UserInformation.objects.filter(user__user=self.request.user).first()
Additionally, when you access UserDetails you don't really need the filter since you are trying to access the related objects from the user itself. So following would work as well.
details = self.request.user.userdetail_related.first()
And as a side note, I think you need OneToOneField here since one user should have only one UserDetails.
As it suggested by #AKS you should use OneToOneField to connect your models to the User. You can do it like this:
class UserDetails(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
related_name='userdetail_related')
details = models.CharField(max_length=100)
class UserInformation(models.Model):
user = models.OneToOneField(
UserDetails,
related_name='userinfo_related')
info = models.CharField(max_length=100, default='this')
Then you can access UserInformation and UserDetails like this:
details = self.request.user.userdetail_related.details
info = self.request.user.userinfo_related.info
To add to the other answers, a few remarks about the layout.
For Model names, best to always use singular (UserDetails -> UserDetail), not plural. Then, for the related name of a ForeignKey, use the plural (because the reverse lookup may find more than one item that have the backwards relationship).
class UserDetail(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='details')
details = models.CharField(max_length=100)
class UserInformation(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='info')
info = models.CharField(max_length=100, default='this')
Makes it much simpler to access in the views
request.user.infos.all().first()
request.user.details.filter(info__startswith="something")
Also, if practical, add both onto the User object, because "flat is better than nested" .
If every User only has one UserDetail and one UserInformation then its best to use OneToOneFields instead.
I would like to create a view with a table that lists all changes (created/modified) that a user has made on/for any object.
The Django Admin site has similar functionality but this only works for objects created/altered in the admin.
All my models have, in addition to their specific fields, following general fields, that should be used for this purpose:
created_by = models.ForeignKey(User, verbose_name='Created by', related_name='%(class)s_created_items',)
modified_by = models.ForeignKey(User, verbose_name='Updated by', related_name='%(class)s_modified_items', null=True)
created = CreationDateTimeField(_('created'))
modified = ModificationDateTimeField(_('modified'))
I tried playing around with:
u = User.objects.get(pk=1)
u.myobject1_created_items.all()
u.myobject1_modified_items.all()
u.myobject2_created_items.all()
u.myobject2_modified_items.all()
... # repeat for >20 models
...and then grouping them together with itertool's chain(). But the result is not a QuerySet which makes it kind of non-Django and more difficult to handle.
I realize there are packages available that will do this for me, but is it possible to achieve what I want using the above models, without using external packages? The required fields (created_by/modified_by and their timefields) are in my database already anyway.
Any idea on the best way to handle this?
Django admin uses generic foreign keys to handle your case so you should probably do something like that. Let's take a look at how django admn does it (https://github.com/django/django/blob/master/django/contrib/admin/models.py):
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'))
change_message = models.TextField(_('change message'), blank=True)
So, you can add an additional model (LogEntry) that will hold a ForeignKey to the user that changed (added / modified) the object and a GenericForeignKey (https://docs.djangoproject.com/en/1.7/ref/contrib/contenttypes/#generic-relations) to the object that was modified.
Then, you can modify your views to add LogEntry objects when objects are modified. When you want to display all changes by a User, just do something like:
user = User.objects.get(pk=1)
changes = LogEntry.objects.filter(user=user)
# Now you can use changes for your requirement!
I've written a nice blog post about that (auditing objects in django) which could be useful: http://spapas.github.io/2015/01/21/django-model-auditing/#adding-simple-auditing-functionality-ourselves