Django model advice for chained ForeignKeys approach - django

I have these models in Django 1.5
class Number(models.Model):
number = models.CharField("Patient's Number", max_length=12, unique=True)
class Appointment(models.Model):
to_number = models.ForeignKey(Number)
message = models.CharField("Message", max_length=160)
I would like the Appointment model to hold another field that will allow me to add how many times the user wants to be notified via email before the actual appointment time (similar to how we add multiple popup/email notifications in Google calendar). Since I'm still a newbie in web development, Django and database models, I have a hard time deciding how to create and link up models to achieve this. One solution that I thought of is to create another model and link it up with Appointment like this:
class Notification(models.Model):
appointment = models.ForeignKey(Appointment)
time = models.TimeField("Time before appointment we must notify the user")
Is that even a sensible approach? If not, what should I do instead? Also, in order to see both Appointment and Notification in Number's view via admin console, what should I declare the Inline stacks (since now that Number --> Apppointment --> Notification, what is the right way to link those up as Inline when viewed under Number's page)? I know something like [this has been asked before] (Nested inlines in the Django admin?), but since it's been asked in 2010, I'm curious as to if anyone has found a new way to do it or if the second answer by #carruthd in the aforementioned link is still the best way to go about it. Thank you.

If you want more notifications for each appointment, add ManyToManyField pointing to another model (Notification in this case). Then you're able to get all upcoming Notifications in this way:
Appointment.notifications_set.filter(notify_time__gte=now()).
class Notification(models.Model):
notify_time = models.DateTimeField("Notification datetime", db_index=True)
class Number(models.Model):
number = models.CharField("Patient's Number", max_length=12, unique=True)
class Appointment(models.Model):
to_number = models.ForeignKey(Number)
message = models.CharField("Message", max_length=160)
notifications = models.ManyToManyField(Notification, through=AppointmentNotifications)
class AppointmentNotifications(models.Model):
notif = models.ForeignKey(Notification)
appoint = models.ForeignKey(Appointment)
There's one more table: AppointmentNotifications, which would be created by Django anyway, but if you create it by yourself, you're able to add some columns later (ie. notification_already_sent = models.BooleanField("User was informed") and you also probably want to display all Notifications joined to each Appointment as an inline:
admin.py:
class AppointmentNotificationInline(admin.TabularInline):
model = AppointmentNotification
class AppointmentAdmin(admin.ModelAdmin):
inlines = [AppointmentNotificationInline]

Related

Create a history record in Django for every UPDATE of a class instance

I have a model in Django that is used to create an item of stock
class Item(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
description = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='item')
amount = models.IntegerField(default=0, blank=False)
place = models.ForeignKey(Place, on_delete=models.CASCADE, related_name='place')
issue_amount = models.IntegerField(default=0, blank=True)
receive_amount = models.IntegerField(default=0, blank=True)
The item amount will be updated everytime an item is issued by adding or subtracting the issue or receive amount in a views function that alters the instance amount and calls save on that instance.
I want to be able to keep a record of every update and make this information available to both my frontend and to an admin model.
I found this tutorial in which the poster creates a separate model with the same field values as Item and then writes SQL commands directly to the database to create a TRIGGER that saves each Item field on every update: https://www.youtube.com/watch?v=d26DUXynf8s
Is there a way I can replicate this same behaviour using Django?
You want an audit log library.
There are a few (I've never been completely satisfied with any of them) but I quite like this one.
As you can see in the docs, you register your model for auditing like so...
from django.db import models
from auditlog.registry import auditlog
class MyModel(models.Model):
pass # Model definition goes here as usual.
auditlog.register(MyModel) # Register the model.
...and then you can access the log for a particular row via the new history property.
log = MyModel.objects.first().history.latest()
You can browse the various other Django audit log options here, and they are all more or less variations on the same theme.
Django already has this exact feature you're looking for with the LogEntry model. All you need to do is to read the data from the Database.
django.contrib.admin.LogEntry

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}')

Django project architecture advice

I have a django project and I have a Post model witch look like that:
class BasicPost(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
published = models.BooleanField(default=False)
created_date = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=False)
body = models.TextField(max_length=999)
media = models.ImageField(blank=True)
def get_absolute_url(self):
return reverse('basic_post', args=[str(self.pk)])
def __str__(self):
return self.title
Also, I use the basic User model that comes with the basic django app.
I want to save witch posts each user has read so I can send him posts he haven't read.
My question is what is the best way to do so, If I use Many to Many field, should I put it on the User model and save all the posts he read or should I do it in the other direction, put the Many to Many field in the Post model and save for each post witch user read it?
it's going to be more that 1 million + posts in the Post model and about 50,000 users and I want to do the best filters to return unread posts to the user
If I should use the first option, how do I expand the User model?
thanks!
On your first question (which way to go): I believe that ManyToMany by default creates indices in the DB for both foreign keys. Therefore, wherever you put the relation, in User or in BasicPost, you'll have the direct and reverse relationships working through an index. Django will create for you a pivot table with three columns like: (id, user_id, basic_post_id). Every access to this table will index through user_id or basic_post_id and check that there's a unique couple (user_id, basic_post_id), if any. So it's more within your application that you'll decide whether you filter from a 1 million set or from a 50k posts.
On your second question (how to overload User), it's generally recommended to subclass User from the very beginning. If that's too late and your project is too far advanced for that, you can do this in your models.py:
class BasicPost(models.Model):
# your code
readers = models.ManyToManyField(to='User', related_name="posts_already_read")
# "manually" add method to User class
def _unread_posts(user):
return BasicPost.objects.exclude(readers__in=user)
User.unread_posts = _unread_posts
Haven't run this code though! Hope this helps.
Could you have a separate ReadPost model instead of a potentially large m2m, which you could save when a user reads a post? That way you can just query the ReadPost models to get the data, instead of storing it all in the blog post.
Maybe something like this:
from django.utils import timezone
class UserReadPost(models.Model):
user = models.ForeignKey("auth.User", on_delete=models.CASCADE, related_name="read_posts")
seen_at = models.DateTimeField(default=timezone.now)
post = models.ForeignKey(BasicPost, on_delete=models.CASCADE, related_name="read_by_users")
You could add a unique_together constraint to make sure that only one UserReadPost object is created for each user and post (to make sure you don't count any twice), and use get_or_create() when creating new records.
Then finding the posts a user has read is:
posts = UserReadPost.objects.filter(user=current_user).values_list("post", flat=True)
This could also be extended relatively easily. For example, if your BasicPost objects can be edited, you could add an updated_at field to the post. Then you could compare the seen_at of the UserReadPost field to the updated_at field of the BasicPost to check if they've seen the updated version.
Downside is you'd be creating a lot of rows in the DB for this table.
If you place your posts in chronological order (by created_at, for example), your option could be to extend user model with latest_read_post_id field.
This case:
class BasicPost(models.Model):
# your code
def is_read_by(self, user):
return self.id < user.latest_read_post_id

Using OneToOneField with multiple versions of data

I want to have two versions of the same model while still benefiting from OneToOneField's reverse relation.
For example, let's say that I have the following models:
class Company(models.Model):
exists = models.BooleanField()
class ExtraInforation(models.Model):
company = models.OneToOneField(Company)
wealthy = models.BooleanField()
At this point my code uses the brilliance of the OneToOneField reverse relation, doing company.extrainformationcalls all over the place.
Then I get a new requirement: we can't trust the ExtraInformation without verifying it first! Pfft, any company could claim that it's wealty...
Any changes to ExtraInformation need to be confirmed before publishing. Let's say that the company isn't wealthy when it registers and that information gets confirmed. Later the company wants to mark itself wealthy. At that point there needs to be the confirmed/public version of ExtraInformation and the unconfirmed version that needs to be confirmed.
I want to be able to still keep those handy OneToOneField reverse relation calls but also have another version of the same data. The problem is, of course, that there can be only one row with reference to this company in the OneToOneField.
Currently my solution is to create a new table:
class ExtraInforationUnconfirmed(models.Model):
company = models.OneToOneField(Company)
wealthy = models.BooleanField()
Once the information is confirmed, the fields are copied from ExtraInforationUnconfirmed to ExtraInformation. This solution isn't very DRY or clean.
What would be the best way to solve this issue?
I studied proxy models and model inheritance. The best alternative way I could think of is to have a base model and inherit two models that have both have OneToOneField relation of their own to Company.
Add a boolean feild to the model and change it to true when confirmed:
class ExtraInforation(models.Model):
company = models.OneToOneField(Company)
wealthy = models.BooleanField()
confirmed = models.BooleanField(default=False)
UPDATE
Based on your comment I suggest a version filed which can be a simple integer or a datetime. I would avoid creating two models at any cost :)
class ExtraInforation(models.Model):
company = models.ForeignKey(Company, related_name='extrainformations')
wealthy = models.BooleanField()
# version = models.PositiveIntegerField()
version = models.DateTimeField(auto_now_add=True)
confirmed = models.BooleanField(default=False)
You can add a property to Company that returns the last extrainformation so that company.extrainformation will still work:
#property
def extrainformation(self):
return self.extrainformations.order_by("-version").first()

Django many-to-many lookup from different models

I have some models that represents some companies and their structure. Also all models can generate some Notifications (Notes). User can see own Notes, and, of course, can't see others.
class Note(models.Model):
text = models.CharField(...)
class Company(models.Model):
user = models.ForeignKey(User)
note = models.ManyToManyField(Note, blank='True', null='True')
class Department(models.Model):
company = models.ForeignKey(Company)
note = models.ManyToManyField(Note, blank='True', null='True')
class Worker(models.Model):
department = models.ForeignKey(Department)
note = models.ManyToManyField(Note, blank='True', null='True')
class Document(models.Model)
company = models.ForeignKey(Company)
note = models.ManyToManyField(Note, blank='True', null='True')
The question is how I can collect all Notes for particular user to show them?
I can do:
Note.objects.filter(worker__company__user=2)
But its only for Notes that was generated by Workers. What about another? I can try hardcoded all existing models, but if do so dozen of kittens will die!
I also tried to use backward lookups but got "do not support nested lookups". May be I did something wrong.
EDIT:
As I mentioned above I know how to do this by enumerating all models (Company, Worker, etc. ). But if I will create a new model (in another App for example) that also can generate Notes, I have to change code in the View in another App, and that's not good.
You can get the Notes of a user by using the following query:
For example let us think that a user's id is 1 and we want to keep it in variable x so that we can use it in query. So the code will be like this:
>>x = 1
>>Note.objects.filter(Q(**{'%s_id' % 'worker__department__company__user' : x})|Q(**{'%s_id' % 'document__company__user' : x})|Q(**{'%s_id' % 'company__user' : x})|Q(**{'%s_id' % 'department__company__user' : x})).distinct()
Here I am running OR operation using Q and distinct() at the end of the query to remove duplicates.
EDIT:
As I mentioned above I know how to do this by enumerating all models
(Company, Worker, etc. ). But if I will create a new model (in another
App for example) that also can generate Notes, I have to change code
in the View in another App, and that's not good.
In my opinion, if you write another model, how are you suppose to get the notes from that model without adding new query? Here each class (ie. Department, Worker) are separately connected to Company and each of the classes has its own m2m relation with Note and there is no straight connection to User with Note's of other classes(except Company). Another way could be using through but for that you have change the existing model definitions.
Another Solution:
As you have mentioned in comments, you are willing to change the model structure if it makes your query easier, then you can try the following solution:
class BaseModel(models.Model):
user = models.Foreignkey(User)
note = models.ManyToManyField(Note)
reports_to = models.ForeignKey('self', null=True, default=None)
class Company(BaseModel):
class Meta:
proxy = True
class Document(BaseModel):
class Meta:
proxy = True
#And so on.....
Advantages: No need to create separate table for document/company etc.
object creation:
>>c= Company.objects.create(user_id=1)
>>c.note.add(Note.objects.create(text='Hello'))
>>d = Document.objects.create(user_id=1, related_to=c)
>>d.note.add(Note.objects.create(text='Hello World'))