Django M2M through table delete and save methods - django

I have a many to many relation between two tables and relation is saved in a through table:
class Book(models.Model):
likes = models.ManyToManyField(User, through=BookLike)
class User(models.Model):
...
class BookLike(models.Model):
user = models.ForeignKey(User)
book = models.ForeignKey(Book)
When a user likes or dislikes a book I want to carry out some actions like increasing/decreasing the like count of book for example. I tried overwriting save and delete functions of BookLike but they are not called when I call book_instance.likes.remove(user) and book_instance.likes.add(user). How can I achieve this?

Wrong approach. If the Book model is connected to User, don't call users likes. Trust me that's going to bite you in the ass down the road.
So, first off:
class Book(models.Model):
users = models.ManyToManyField("User", through="BookUser", through_fields=('book', 'user'), related_name="books")
class User(models.Model):
...
class BookUser(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="bookusers")
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name="bookusers")
Then, you don't need to overwrite the default model methods. All you need to do is to register books in users or viceversa exactly how you described.
user_instance.books.add(book_instance)
Then if you want to retrieve or count how many likes a book has you can run a simple query:
book_instance.users.count()
book_instance.users.all()
or
BookUser.objects.filter(book=book_instance).count()
or the exact opposite if you want to fetch info about a specific user

Related

How to define models in django that provides custom values based on pre-selection for a field?

Given the following model that stores the user's wish list for reading books:
class ReadingList(models.Model):
user_id = models.ForeignKey(UserInfo, on_delete=models.DO_NOTHING, null=False, blank=False, default=None, db_column='user_id')
book= models.CharField(max_length=255, blank=False)
creation_time = models.DateTimeField(blank=True)
class Meta:
unique_together = (('user_id', book),)
I want to create a model that helps in tracking the time spent in the reading the book on different days which looks something like this:
class ReadingTracker(models.Model):
user_id = models.ForeignKey(ReadingList, on_delete=models.DO_NOTHING, related_name='user', blank=False, db_column='user_id')
book= models.ForeignKey(ReadingList, on_delete=models.DO_NOTHING, related_name='book-to-read', blank=False, db_column='book')
time = models.DateTimeField(blank=True)
time_spent = models.floatfield()
On the client-side (corresponding to ReadingTracker) for both the fields user_id and book
I see that ReadingList object (1), ReadingList object (2), ... are listed. But, this is not working as expected.
What I want to achieve are the following:
For user_id field I want to see the something like dummy_uid1, dummy_uid2, ... to be listed.
Consider dummy_uid1 wants to read book1 and book2 whereas dummy_uid2 wants to read book1 and book3.
When dummy_uid1 is selected as user_id, I want only book1 and book2 to be listed for selection.
How do I define the model in django rest framework to achieve this?
Any suggestions related to the above would be much appreciated and thank you in advance.
There are two parts to this question:
If you want to see a different value than ReadingList object (1) then you need to define the __str__ value of your model, you can do this like so:
class ReadingList(models.Model):
...
def __str__(self):
return f'{self.user_id}' # return whatever string you want to display
If you want to just display the books for a particular user then you can use a filter() (see the Django documentation):
reading_list = ReadingList.objects.get(...)
ReadingTracker.objects.filter(user_id=reading_list)
However, I would add that you have a user_id on your ReadingList object which does seem to connect to a User model, but your user_id on ReadingTracker is a ForeignKey relation to ReadingList, which is confusing. I would suggest renaming the field or actually making it link to the User model (though this is unnecessary as you can still filter by User through the ReadingList model).

How to call a a field of one model A into another model B so that b can work as a view

I have created a model called Department, Course. Models are as follow
This is the model for departments and course
class Departments(models.Model):
Department_Id = models.IntegerField(primary_key=True)
Department_Name = models.CharField(max_length=200)
Department_Code = models.CharField(max_length=200)
class Course(models.Model):
Course_Id = models.IntegerField(primary_key=True)
Department_Id = models.ForeignKey(Departments, on_delete=models.CASCADE)
Course_Name = models.CharField(max_length=200)
Course_Code = models.CharField(max_length=200)
I want to create a model called view which can be later on called for search. I want a view model in a such a way that it consit of the data in concat form i.e. name= Department_name+ Course_Name
class View (models.model):
view_id= models.IntegerField(primary_key=True)
Name= Department_name(I want this from Departments table)
+ Course_Name(I want this from Course table)
I try using one to one relation . I would really appricate the help
It's not clear why you'd want to do that. It's never a good idea to duplicate data from one model into another one, as it can lead to inconsistencies.
You can add a ForeignKey in View to your Course model and then when you do f"{view.course.name} {view.course.department.name}" you already have your string:
class View(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE)
def name(self):
return f"{self.course.name} {self.course.department.name}"
Notes:
Don't call your foreign key Department_id because it's not referring to the id but to the object itself in the Django ORM: department = models.ForeignKey(Department, on_delete=models.CASCADE). As you can see, this makes reading the code much simpler: self.course.Department_id is a Department object not an integer, so self.course.department makes more sense.
Don't prefix your field names with the class, it just makes the code so much less readable: Do you prefer department.name or department.Department_name?
The View model is still a mystery to me, as you can search without it. You can search for example for courses with a matching department name like this:
Course.objects.filter(department__name__icontains="maths")
which will return all courses with "maths" in their department name.
Remove all the ids from your models, they are created automatically by Django anyway (and called id). Again, department.id is much easier to read than department.Department_id. Also in your code, you have to generate the ids yourself since you don't set them to auto-populate.

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 model design - ManyToMany or ForeignKey

I am confused to take a decision whether to use ForeignKey or ManyToManyField.
Suppose I am building an application for an event which demands tickets to get access the event and delegates may get some coupon based on the category of the ticket they have taken. I might have the following classes:
class Coupon(models.Model):
name = models.CharField()
event = models.ForeignKey(Event)
created_by = models.ForeignKey(User)
expired_time = models.DateTimeField()
description = models.TextField()
created_at = models.DateTimeField()
class CouponTicketMap(models.Model):
coupon = models.ForeignKey(Coupon)
tickets = models.ManyToManyField(Ticket)
class CouponUserMap(models.Model):
coupon = models.ForeignKey(Coupon)
users = models.ManyToManyField(User)
Organizer can map coupons to one or more tickets.
Or/And he can map to some selected or random users.
(I do not need an extra field in the intermediate table that is why I did not use through here.)
I can redesign the 2nd and 3rd model as
class CouponTicketMap(models.Model):
coupon = models.ForeignKey(Coupon)
tickets = models.ForeignKey(Ticket)
class CouponUserMap(models.Model):
coupon = models.ForeignKey(Coupon)
users = models.ForeignKey(User)
I think I can achieve what I need from both design, but want get know about the consequences of both design. So which design will get more votes when considering aspects such as performance, storage, conventional style etc. Can anybody shed some light on making a decision.
Thanks
I´ll say this model due to what you say:
class CouponTicketMap(models.Model):
coupon = models.ForeignKey(Coupon)
tickets = models.ForeignKey(Ticket)
class CouponUserMap(models.Model):
coupon = models.ForeignKey(Coupon)
users = models.ManyToManyField(User)
Cuz, one coupone can have many tickets, and many users can have a related same coupon. Dont see neccesary to stick just to one parameter, when you can use them both depending of the designed needed. Hope my opinion helps.

User as a subclass in Django

I'm working on a Django project for a medical team and have to include a calendar app to manage patients' appointments. Generally, a calendar is composed of several events with one or several attendees who are all Users. Here, attendees can be both doctors and patients. For practical and security reasons, they are separated in two different models: auth.User for medical staff, and Patient for the patients. My idea was therefore to create an abstract class called People on top of User and Patient:
class Event(models.Model):
title = models.CharField(max_length=255)
start = models.DatetimeField()
end = models.DatetimeField()
#...
class Attendee(models.Model):
event = models.ForeignKey(Event)
people = models.ForeignKey(People)
attendance = models.CharField(choices = ("Yes","No","Unknown"))
# Abstract class
class People(models.Model):
name = models.CharField(max_length=100)
#...
class Meta:
abstract = True
# Patient is a subclass of People
class Patient(People):
date_of_birth = models.CharField(max_length=100)
#...
Question is : How could I now make Auth.User also a subclass of People so that User could also be considered as attendees? Is it even possible? Otherwise how would you manage this issue?
You might want to turn things around, and inherit the medical staff model from AbstractUser and from People, like so class Staff(AbstractUser, People). This way, Staff has both the People model attributes and methods, and it still has the django User features.