Auto-create model with foreign key when model is created - Django - django

I am making a comments section on my webpage and want users to be able to upvote or downvote a comment.
My models are as such:
class Comment(models.Model):
owner = models.ForeignKey(User)
body = models.TextField(null=True, blank=True, max_length=500)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Vote(models.Model):
comment = models.ForeignKey(Comment)
upvote = models.SmallIntegerField(null=True, blank=True, default=0)
downvote = models.SmallIntegerField(null=True, blank=True, default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
When a user posts a comment, I want it to also create a Vote model that is linked to that comment.
I am new to django and programming but from my understanding, I need to create a save hook or something similar?

You can override the save() method of Comment model, ie:
class Comment(models.Model):
...
def save(self, **kwargs):
super(Comment, self).save(**kwargs)
vote = Vote(comment=self)
vote.save()
I suggest you to read the documentation for a better insight.

Consider this code:
class ModelA(models.Model):
name = models.CharField(max_length=30)
#classmethod
def get_new(cls):
return cls.objects.create().id
class ModelB(models.Model):
thing = models.OneToOneField(ModelA, primary_key=True, default=ModelA.get_new)
num_widgets = IntegerField(default=0)
Of course you can use lambda as well, as long as you return integer id of related object. I don't recommend overwritting save method.

Related

Django - ForeignKey Filter Choices

I'd like to filter the choices that a user can choose in my ForeignKey Field.
I basically have a ForeignKey for the subject of the Test and the actual topic of the Test. These topics come from a different model and are linked to a subject. Now I'd like to filter the choices to only include the topics that are linked to the currently selected subject. Is that possible and if so, how?
models.py
class Test(models.Model):
student = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True)
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, blank=True, null=True)
grade = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(6)], blank=True, null=True)
date = models.DateField(default=datetime.date.today)
def save(self, *args, **kwargs):
if not self.school_class and self.student:
self.school_class = self.student.klasse
return super().save(*args, **kwargs)
class Thema(models.Model):
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.CharField(max_length=50)
class Subject(models.Model):
teacher = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(max_length=20)
The Problem if I use this:
# thema model #staticmethod
def return_thema(subject):
themen = Thema.objects.filter(subject=subject)
return {'thema': themen}
#test model
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True,limit_choices_to=Thema.return_thema(subject))
Is that I get the Error:
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Meaning I can't get the objects of the Thema Model while the models are loading
EDIT (for Swift):
That seemed to resolve the error when trying to makemigrations, but I now get this error, when visiting the admin portal to create a new Test:
File "/Users/di/Code/Schule/GymnasiumApp/venv/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1404, in build_filter
arg, value = filter_expr
ValueError: too many values to unpack (expected 2)
I think what you are looking for ideally would be ForeignKey.limit_choices_to
Please see the docs:
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.limit_choices_to
You can limit the choices available at a model level, which is enforced throughout the django app, including forms automatically.
Edit because OP provided more information
Ok so I believe if you declare the thema field on the test model like so, it will solve the issue, and I will explain why after:
class Test(models.Model):
student = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True, limit_choices_to=Q('thema_set__subject_set'))
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, blank=True, null=True)
grade = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(6)], blank=True, null=True)
date = models.DateField(default=datetime.date.today)
def save(self, *args, **kwargs):
if not self.school_class and self.student:
self.school_class = self.student.klasse
return super().save(*args, **kwargs)
We are essentially telling Django to evaluate the relationship between the limited choices "lazily" I.e. when the form is loaded dynamically. Django forms will look at the limit_choices_to field argument and apply it to the available choices.
I'm not 100% about the relationship of your models so the Q(...) I added, might actually need to be Q('subject_set')
If you use django forms you can use the model choice field.
In your view you can set your queryset of this choicefield. Zo you can filter it.
fields['your model field'].queryset = yourmodel.objects.filter(your filter parameters)
I think there is also problem in save method also. Aren't you need to write the name of the model inside like
return super(<modelName>).save(*args, **kwargs)

best way to implement ManyToManyField check?

Want to create a Song object. How do I check if this Song object is in a particular user's UserProfile to avoid repetition?
tried this but threw me an error argument of type 'QuerySet' is not iterable;
songs_available = user.userprofile.liked_songs.all()
if not song in songs_available:
user.userprofile.liked_songs.add(song)
models.py
class Song(models.Model):
track_name = models.CharField(max_length=250)
artiste_name= models.CharField(
max_length=200)
album = models.ForeignKey(Album, on_delete= models.CASCADE, null=True, default=None, related_name =
"songs")
class Meta:
db_table="Song"
def __str__(self):
return self.track_name
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, default=None , null= True)
liked_songs = models.ManyToManyField("Song", null=True , default=None,
related_name="users_that_liked_me")
class Meta:
db_table = "UserProfile"
def __str__(self):
return self.user.username
views.py (part of it)
song, created = Song.objects.get_or_create(track_name = track,
artiste_name = artist,
album = album)
wanted to try if created but that only checks for the song model as many users could have the same song, it doesnt really help
You can do this, because I have improved your code
class Song(models.Model):
track_name = models.CharField(max_length=250)
artiste_name= models.CharField(
max_length=200)
album = models.ForeignKey(Album, on_delete= models.CASCADE, null=True,
related_name="songs")
class UserProfile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null= True)
liked_songs = models.ManyToManyField("Song"related_name="users_that_liked_me")
refer to Django's docs for many-to-many field management.
here you can't directly add a many-to-many fields.
first, you need to create an object then add many-to-many data to it.
https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_many/
So according to the Django Docs, as #iklinac stated in the comments, .add() prevents duplicate creation when populating a many to many field, as in my case. Eventually solved the problem like this.
song, created = Song.objects.get_or_create(track_name = track,
artiste_name = artist,
album = album)
user.userprofile.liked_songs.add(song)
All other code in my question remains the same.

How to post to HyperlinkedRelatedField in Django Rest Framework?

Here I have two models called PostComment and AnswerComment separately to handle comments in my web application. Now I need to have voting option for both Post and Answer comments. Therefore I though using Django GenericRelations here would be a good idea to continue. I have implemented all the parts and I can't use Django Rest Framework to post data with Django HyperlinkedRelatedField. I'm using rest-framework-generic-relations (link) app as DRF documentation has recommended it. It gives me following error when I try to post data.
Followings are my implementations,
Post and Answer Models,
class PostComment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class AnswerComment(models.Model):
answer = models.ForeignKey(Answer, on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Comment Model,
class VoteType(models.TextChoices):
EMPTY = 'EMPTY'
LIKE = 'LIKE'
DISLIKE = 'DISLIKE'
class CommentVote(models.Model):
voteType = models.CharField(max_length=10, choices=VoteType.choices, default=VoteType.EMPTY)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
voted_object = GenericForeignKey('content_type', 'object_id')
Serializer for vote (as implemented in the documentation),
class CommentVoteSerializer(serializers.ModelSerializer):
voted_object = GenericRelatedField({
AnswerComment: serializers.HyperlinkedRelatedField(
queryset=AnswerComment.objects.all(),
view_name='answercomment-detail'
),
PostComment: serializers.HyperlinkedRelatedField(
queryset=PostComment.objects.all(),
view_name='postcomment-detail'
),
})
class Meta:
model = CommentVote
fields = ['voted_object', 'voteType', 'owner']
View for vote,
class CommentVoteViewSet(viewsets.ModelViewSet):
queryset = CommentVote.objects.all()
serializer_class = CommentVoteSerializer
Urls for vote,
router.register(r'comment_vote', CommentVoteViewSet, basename='comment_vote_api')
Any help would be great. Thank you!

Django: How to generate unique order id

This is how my model looks like. When ever user orders. The order id provided by django is simple. Its like 1,2,3 ..... 100.
class UserOrder(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='orders',
on_delete=models.CASCADE)
cart = models.ForeignKey(Cart, related_name="orders", on_delete=models.CASCADE, null=True,
blank=True)
date = models.DateTimeField(default=datetime.now)
total_price = models.IntegerField(null=True, blank=True)
note = models.TextField(null=True, blank=True)
cancel_reason = models.TextField(null=True, blank=True)
cancelled = models.BooleanField(default=False)
confirmed = models.BooleanField(default=False)
def __str__(self):
return self.user.username
Your question is not well defined but I think I understand your problem, you need a id (lookup field) that is not simple.
you can use uuid, and use this uuid for all the lookup in the viewsets, instead of the sequentially created id by django.
something like this lookup_field = 'uuid' in views.
import uuid
class UserOrder(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, unique=True, db_index=True, editable=False)
read more about uuids here https://docs.python.org/3/library/uuid.html#uuid.uuid4
try using the django-random-id-model
run pip install django-random-id-model
in your django models.py file import RandomIDModel from django-random-id-model
then add RandomIdModel to your model class, in case this is:
class UserOrder(RandomIdModel):
#remaining codes here

Best way to make "viewed" attribute for messages inside user group?

I am deciding on how to track if a user has seen a post in the timeline or not.
There is Post and Comment model like this.
class Comment(models.Model):
author = models.ForeignKey('UserProfile')
title = models.CharField(max_length=255)
text = models.TextField(null=True, blank=True)
date_created = models.DateTimeField()
post = models.ForeignKey('Post', related_name='comments')
class Post(ContentTypeModel):
title = models.CharField(max_length=255)
group = models.ForeignKey('UserGroup', null=True)
date_updated = models.DateTimeField()
Suggestions about best practices on how to track if post has been seen by particular member of a user group will be nice.
I managed to do as was suggested, i.e. adding m2m relation to track who viewed the post:
class Post(models.Model):
...
date_updated = models.DateTimeField()
viewers = models.ManyToManyField('UserProfile', through='PostViewer')
class PostViewer(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
viewer = models.ForeignKey('UserProfile', on_delete=models.CASCADE, related_name='posts_seen')
last_seen = models.DateTimeField(null=True, blank=True, help_text='Is set when user sees the post in details')
class Meta:
unique_together = ('post', 'viewer')
This way i can compare post.date_updated to viewer.last_seen and get desired filtering on who have seen the update/create of Post.