Designing without duplicate foreignkey references in Django - django

I want to create an expense tracking application, where each user can enter expenses and classify them into his own categories. Here is the model definition that I use:
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
user = models.ForeignKey(User, related_name="my_categories")
name = models.CharField(max_length=50)
class Expense(models.Model):
date = models.DateField()
amount = models.IntegerField()
description = models.TextField()
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
Each category will have to be associated with a user so that we can display each user his own categories to choose while entering the expense. For similar reasons, each expense record should be associated with a user. But in definition of Expense, we are having 2 references to the User models, one directly through 'user' field and another through the 'category' field which has a user reference.
I believe that multiple references like this is a bad thing. Is there a better way to model this? I understand that we can find out the user from the category reference, but it seems a roundabout way of doing it.

Although your db is not 100% normalized, in your case, I do not believe the second reference is redundant. Both 'an expense' and 'a category' are well defined entities, which belong to a user. If you will later want to change your foreign key to allow a null category or to a ManyToManyField, you will notice immediatly that both user fields are required. Lookups by user are also much easier for the db and developer when the column is there.

Related

Django Model Reference to Multiple Foreign Key with Child Property

I have the following Models of customer details with each customers having different payment modes (eg. cash, online transfer, etc...) :
class Customer(models.Model):
#some customer details
class Payment(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.RESTRICT, related_name='payments')
payment_mode = models.CharField(max_length=255, blank=True, null=True)
And I would like to add a new Invoice Model to include the customer as well as the customer's payment modes.
class Invoice (models.Model):
customer = models.ForeignKey(Customer, on_delete=models.RESTRICT, related_name='payments')
payment_mode = models.ForeignKey(Customer.payments.payment_mode, on_delete=models.RESTRICT)
I am going to create an Invoice for a customer and will input the customer's available payment mode. But the the invoice's payment mode is giving me an AttributeError: 'ReverseManyToOneDescriptor' object has no attribute 'payment_mode'.
May I know how do I set up the reference to the customer's child data?
Thank you.
What you have is not a valid field definition.
You can reference Payment directly
payment_mode = models.ForeignKey(Payment, on_delete=models.RESTRICT)
Why is the Invoice model even required? You have the same fields already in the existing models.
You can simply make a view to do this.
For example,
def show_invoice(request, pk):
# this pk value should be sent by your frontend and represents your Primary Key value for the current customer
customer = Customer.objects.get(pk=pk)
return render(request, 'some_html_page.html', {'customer': customer})
Now in your some_html_page.html, you can show the details you want using the 'context' to the render function ({'customer': customer}) we just passed.
Models in Django should be thought of as Tables in SQL; you are not supposed to make a new one every time you want to "infer" something, rather you should make them when you want to 'store' something (or normalize existing tables/models). The Invoice model does not do either of these two things.
You can give this a read to understand how to write views in Django this.
In the case where you really want to make an Invoice model you don't need to make a payment_mode field, since you're already pointing to the Customer model which is, in turn, pointing to the Payment model.

Multiple-valued field in Django?

I have my model Room as follows:
eid = models.CharField(max_length=64, unique=True)
name = models.CharField(max_length=25)
I want to create another field user where user is a foreign key. Is to possible to create a field where multiple values can be added to a single row? For example: there are 3 users: user1,user2,user3.
Now, I want a row to look like:
{'eid':1,'name':'room1','user':[user1,user2,user3]}
Is the above scenario possible or do I need to create separate rows for each user?
Note: A room model can have multiple users, but a user cannot be part of multiple rooms.
If you want to have multiple foreign keys, you can use ManyToManyField. When you convert it to JSON, it will display as array, just like you wanted.
It looks like a classical many-to-many relationship. As the Django docs say for that case, you could have your model Room as something like this:
eid = models.CharField(max_length=64, unique=True)
name = models.CharField(max_length=25)
users = models.ManyToManyField(User)
EDIT: since the OP has mentioned in a comment that one user must have only one room, but that a room may have many users, then their User model must have a ForeignKey field, as it is a one-to-many relationship:
room = models.ForeignKey(Room, on_delete=models.CASCADE)
Possible values for the on_delete field may be checked in the docs.

Django Query ManyToMany with Custom Through Table Field Data

I've been trying to figure this one out for a while now but am confused. Every ManyToMany relationship always goes through a third table which isn't that difficult to understand. But in the event that the third table is a custom through table with additional fields how do you grab the custom field for each row?
Here's a sample table I made. How can I get all the movies a User has watched along with the additional watched field and finished field? This example assumes the user is only allowed to see the movie once whether they finish it or not so there will only be 1 record for each movie they saw.
class Movie(models.Model):
title = models.CharField(max_length=191)
class User(models.Model):
username = models.CharField(max_length=191)
watched = models.ManyToMany(Movie, through='watch')
class Watch(models.Model):
user = models.Foreignkey(User, on_delete=models.CASCADE)
movie = models.Foreignkey(Movie, on_delete=models.CASCADE)
watched = models.DateTimeField()
finished = models.BooleanField()
Penny for your thoughts my friends.
You can uses:
from django.db.models import F
my_user.watched.annotate(
watched=F('watch__watched'),
finished=F('watch__finished')
)
This will return a QuerySet of Movies that contain as extra attributes .watched and .finished.
That being said, it might be cleaner to just access the watch_set, and thus iterate over the Watch objects and access the .movie object for details about the movie. You can use .select_related(..) [Django-doc] to fetch the information about the Movies in the same database query:
for watch in my_user.watch_set.select_related('movie'):
print(f'{watch.movie.title}: {watch.watched}, {watch.finished}')

Database normalization in django

I need an optimally normalized database structure to achieve the following requirement.
models.py
class Learning_Institute(models.Model):
name = models.TextField()
user = models.ManyToManyField(settings.AUTH_USER_MODEL)
class Course(models.Model):
title = models.CharField(max_length=50)
instructor = models.ForeignKey(User, on_delete=models.PROTECT, related_name='courses_taught')
institute = models.ForeignKey(Learning_Institute, on_delete=models.PROTECT, related_name='courses')
I need the instructor field in the Course table to be limited to the set of users in Learning_Institute instead of all the users in the system.
How do I achieve this on the DB level?
I don't think that you can limit in the model itself.
One of the things that you can do is on form save to have validations using form clearing methods like so
And you can create a check that does something like this:
def clean_ instructor(self):
instructor = self.cleaned_data['instructor']
if instructor.type != "instructor":
raise forms.ValidationError("The user is not instructor!")
Another option is to create another User object that will inherit User and you can call it InstrcutorUsers
I have used this tutorial to extend the user model in django
I don't know if it's suitable for your scenario but changing the relations slightly may achieve what you want.
Removing the many to many for User and create a concrete association model for it, will
at least make sure the Course can only have users that also are instructors, by design.
Consider the following model structure:
class LearningInstitute(models.Model):
name = models.TextField()
class InstituteInstructor(models.Model):
class Meta:
unique_together=('user','institute')
user = models.ForeignKey(User, on_delete=models.PROTECT)
institute = models.ForeighKey(LearningInstitute, on_delete=models.PROTECT)
class Course(models.Model):
title = models.CharField(max_length=50)
instructor = models.ForeignKey(InstituteInstructor, on_delete=models.PROTECT)
You have LearningInstitutes
A user can be an instructor with a related institute, a User can only be related to the same institute once
A Course can only have an instructor (and by that also the related institute)
Design can easily be extended to let Courses have multiple instructors.
By design the Course can only have users that are also instructors.
There is a possibility in Django to achieve this in your model class. The option that can be used in models.ForeignKey is called limit_choices_to.
First I'd very strongly recommend to rename the field user in the class LearningInstitute to users. It is a many to many relation, which means an institute can have many users, and a user can perform some work in many institutes.
Naming it correctly in plural helps to better understand the business logic.
Then you can adapt the field instructor in the class Course:
instructor = models.ForeignKey(
'User', # or maybe settings.AUTH_USER_MODEL
on_delete=models.PROTECT,
related_name='courses_taught',
limit_choices_to=~models.Q(learning_institute_set=None)
)
This is not tested and probably will need some adjustment. The idea is to get all User objects, where the field learning_institute_set (default related name, since you haven't specified one) is not (the ~ sign negates the query) None.
This has however nothing to do with normalisation on the database level. The implementation is solely in the application code, and the database has no information about that.
As suggested by #TreantBG, a good approach would be to extend the class User and create class Instructor (or similar). This approach would affect the database by creating an appropriate table for Instructor.

Django: distinct QuerySet based on a related field

In my Django app I allow users to create collections of movies by category. This is represented using 3 models, Movie, Collection, and Addition (the Addition model stores movie, collection, and user instances). Simplified versions of all three models are below.
class Movie(models.Model):
name = models.CharField(max_length=64)
class Collection(models.Model):
name = models.CharField(max_length=64)
user = models.ForeignKey(User)
class Addition(models.Model):
user = models.ForeignKey(User)
movie = models.ForeignKey(Movie)
collection = models.ForeignKey(Collection)
So for example a user could create a collection called "80's movies", and add the movie "Indiana Jones" to their collection.
My question is: how do I display a distinct list of movies based on a set of query filters? Right now I am getting a bunch of duplicates for those movies that have been added to more than one collection. I would normally use distinct() to get distinct objects, but in this case I need distinct movies rather than distinct additions, but I need to query the Addition model because I want to allow the user to view movies added by their friends.
Am I setting up my models in an optimal way? Any advice/help would be much appreciated.
Thanks!
First. I don't think you need Addition model here. You try to create many-to-many relation, but there's documented way of doing this:
class Movie(models.Model):
name = models.CharField(max_length=64)
class Collection(models.Model):
name = models.CharField(max_length=64)
user = models.ForeignKey(User)
movies = models.ManyToManyField('Movie', blank=True, null=True)
Second. The documentation says: "To refer to a "reverse" relationship, just use the lowercase name of the model".
So the answer is (for the setup above):
Movie.objects.filter(collection__user=user).distinct()