Django follow feature with m2m - python

I tried to solve the problem and got stuck. The problem is that I have a post that I can follow. My problem is that I don't know how to add a tracking button. Should this be done by url, with a view? Or should it be rather as a method in the model?
My problem is also whether it is properly written in terms of models - using the intermediate model Follower?
Here is Post model and I would like to add followers here. I mean, everybody who is interested, can follow this post.
class Post(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='posts')
title = models.CharField(max_length=255, unique=True)
description = models.TextField(max_length=1024)
followers = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Follower', blank=True)
is_visible = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('posts:post_detail', kwargs={'pk': self.pk})
def number_of_followers(self):
return self.followers.count()
Here is my manager for follower model:
class FollowerManager(models.Manager):
use_for_related_fields = True
def follow(self, user, pk):
post_object = get_object_or_404(Post, pk=pk)
if user.is_authenticated():
if user in post_object.followers.all():
Follower.objects.filter(post=post_object, user=user).delete()
else:
Follower.objects.create(post=post_object, user=user)
Here is Follower model:
class Follower(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
objects = FollowerManager()

Interactions between a user's browser and the database can only be done via a URL and a view. That view might call a model method, but there is no possible way for the browser to call that method directly.
(Also I don't understand what you're doing in the manager. Why are you deleting followers if the user is authenticated? Note that will always be true, so the followers will always be deleted.)

Related

Using User Profile automatically load information into form fields

In my pervious question I asked how I can automatically save the user submitting the form. I found the form_valid method to be the best in that case. However in my models I also have a user profile model like this
models.py
....
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
title = models.CharField(max_length=24)
first_name = models.CharField(max_length=35)
last_name = models.CharField(max_length=35)
email = models.EmailField(max_length=64)
phone_number = models.CharField(max_length=12)
department = models.ForeignKey(Department,null=True,on_delete=models.SET_NULL)
supervisor = models.ForeignKey('self',blank=True,null=True,on_delete=models.SET_NULL)
...
As you can see I used the One to One method to make my UserProfile
As before in my models.py I have my reports model
...
class Report(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid1,editable=False)
department = models.ForeignKey(Company,null=True,on_delete=models.SET_NULL)
user= models.ForeignKey(User,on_delete=models.PROTECT)
submission_date= models.DateField(auto_now=True) #invisible to user
submission_time = models.TimeField(auto_now=True) #invisible to ,user
date = models.DateField(default=now,blank=False)
time = models.TimeField(default=now,blank=False,help_text="hh:mm:ss")
location = PlainLocationField()
building = models.ForeignKey(bld,null=True,on_delete=models.SET_NULL)
size = models.PositiveIntegerField()
notes = models.TextField(blank=True)
def __str__(self):
return f'{self.date} {self.time} ({self.department})
...
My question how I can make it so that the department field will load from the user profile? I would hope to eventually make it possible for users in the same department to be able to view and update each others Reports.
As before:
form.py
class ReportForm(forms.ModelForm):
class Meta:
model = Report
fields = '__all__'
location = PlainLocationField()
def redirect():
return redirect("Report")
views.py
class ReportCreate(LoginRequiredMixin,CreateView):
Template = "templates\reports\Report.html"
model = Report
fields = '__all__'
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.save()
return super(ReportCreate, self).form_valid(form)
def get_success_url(self):
return reverse('Report')
def get_absolute_url(self):
return reverse('Report', kwargs={'pk':self.pk})
I advise you to use related_name in your ForeignKeys. Set the department field of both models as following:
class Profile(models.Model):
...
department = models.ForeignKey(Department, null=True, on_delete=models.SET_NULL, related_name='profiles')
...
class Report(models.Model):
...
department = models.ForeignKey(Department, null=True, on_delete=models.SET_NULL, related_name='reports')
...
From now on, Department objects that are related to User.Profile you can access like that:
Department.profiles.all() # it returns QuerySet of all related to Department Profile objects
Department.reports.all() # it returns QuerySet of all related to Department Report objects
And you can use it in making QuerySet for user:
Report.objects.filter(department=self.request.user.profile.department)
# it returns all Report objects, that have exactly the same department as the user
Or using our new relationship:
department = self.request.user.profile.department
reports_for_user = department.reports.all()
But I can see one problem. You are using Company model for ForeignKey in Report. It has to be the same Department model for both Profile and Report models for such easy option to work. Also you definitely should not mix naming in single project. You can set relation with Company as another field:
company = models.ForeignKey(Company, null=True, on_delete=models.SET_NULL)
Here are some steps to help you autofill some fields:
Get the user from self.request.user. How to access current user in Django class based view
Get the profile: get user profile in django
Pass the required fields as context variables: https://www.geeksforgeeks.org/how-to-pass-additional-context-into-a-class-based-view-django/
Pass it into javascript. How can I pass my context variables to a javascript file in Django?
Set the value like this: Set the value of an input field
DONE!

Attribute Error When Clicking Model in Admin Section of Django

I'm using Django and I'm getting the error AttributeError at /admin/network/post/
'Post' object has no attribute 'user'
The strange thing is this error happens when I'm looking at the admin section, and clicking 'Posts.' I only have models for users and posts. Not sure how to fix this error because so far I've never gotten an error like this when clicking it in the admin section of the site: http://127.0.0.1:8000/admin/
I think the issue is in my model because the view for creating a post works totally fine.
models.py
class User(AbstractUser):
pass
class Post(models.Model):
text = models.TextField(max_length=500, blank=True, null=True)
username = models.ForeignKey('User', on_delete=models.CASCADE,
related_name='author',
null=True, blank=True)
timestamp = models.DateTimeField(auto_now_add=True)
like = models.ManyToManyField(
User, blank=True, related_name="liked_user")
def __str__(self):
return self.user.username
class Follow(models.Model):
target = models.ForeignKey('User', on_delete=models.CASCADE,
related_name='followers')
follower = models.ForeignKey('User', on_delete=models.CASCADE,
related_name='targets')
views.py
def make_post(request):
if request.method == "GET":
form_for_post = {'form': PostForm()}
return render(request, "network/make_post.html", form_for_post)
else:
form = PostForm(request.POST)
if form.is_valid():
text = form.cleaned_data['text']
new_post = Post.objects.create(
text=text,
username=request.user,
)
return render(request, "network/make_post.html", {
"new_post": new_post,
})
You defined the field that refs to a User in the Post model to be username, not user, although user should be a better idea.
You thus should implement the __str__ method as:
class Post(models.Model):
# …
username = models.ForeignKey('User', on_delete=models.CASCADE, related_name='author', null=True, blank=True)
# …
def __str__(self):
return self.username.username
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Django - Create related object on first call

I have some models:
class User(AbstractUser):
cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True)
class Cart(models.Model):
products = models.ManyToManyField('product.Product')
date_updated = models.DateTimeField(auto_now=True, editable=False)
Now I need to create user cart instance at first call.
if request.user.cart is None:
request.user.cart = Cart.objects.create()
request.user.save()
This method is not good for me, because it leads to code duplication (every time I need to import Cart model and check the user's cart is it None or not).
The best way that I can find is AutoOneToOneField in django-annoying, but unfortunately it absolutely broke current field autocomplete in PyCharm.
What is the right way to do that?
P.S. I really don't need to create user cart object at user creation moment.
P.P.S. Sorry for my bad English.
UPDATE: Thank you very much! I came to the first code fragment on my own, but it is noob edition script:
class User(AbstractUser):
_cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')
#property
def cart(self):
return self._cart # just for test
...end in my view i've got error like "object Cart has no property 'products'". But next code works great
class User(AbstractUser):
_cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')
#property
def cart(self):
if not self._cart:
self._cart = Cart.objects.create()
self.save(update_fields=('_cart',))
return self._cart
...except "unresolved attribute reference" warning in view:
if request.user.cart.add(type, pk):
messages.success(request, 'Added to cart')
but anyway, that warning should be PyCharm bug (do not autocomplete any #property-decorated methods). You are great, thank's a lot!
There's a couple different ways I can think of off the cuff:
A middleware that does the check on every request – clean, but causes extra database hits even if that request doesn't need the cart on the user
A separate function get_cart(request) -> Cart that does the check
An accessor on your custom User:
class User(AbstractUser):
_cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')
#property
def cart(self):
if not self._cart_id:
self._cart = ...
self.save(update_fields=('_cart',))
return self._cart
Also, you might want to consider
class Cart(models.Model):
user = models.OneToOneField(User)
products = models.ManyToManyField('product.Product')
date_updated = models.DateTimeField(auto_now=True, editable=False)
# and
class User(AbstractUser):
#cached_property
def cart(self):
cart, created = Cart.objects.get_or_create(user=self)
return cart
instead.

Django model for voting system and follow feature

I work on the voting system (vote up and vote down) and the functionality - follow.
I want it to be done well, because I don't have anyone to advise, I put the post and code here.
Follow function - it should show how many followers there are and who they are. I used here a m2m relation with the intermediate model Follower.
My question - is this the correct approach to the topic - using m2m with an intermediate model here?
Functionality vote up and vote down - it is supposed to show how many votes up and how many down and who voted down and who voted up.
My question is whether there is also OK here with the relation between m2m and the intermediate model Voter?
Code for follow feature:
class Post(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='posts')
title = models.CharField(max_length=255, unique=True)
description = models.TextField(max_length=1024)
followers = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Follower', blank=True)
is_visible = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('posts:post_detail', kwargs={'pk': self.pk})
def number_of_followers(self):
return self.followers.count()
class Follower(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user
Code for vote up and vote down feature:
class Question(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
vote_up = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Voter', blank=True)
vote_down = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Voter', blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('qa:qa_detail', kwargs={'pk': self.id})
class Voter(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user
Now is working only follow feature but I want to make sure my approch is ok. Please and thanks for your help.
Currently there is nothing that differentiates the up_vote from the down_vote on the Question model so this will return the same query.
As a side note if you plan to add similar voting/following functionality to other models it may be worth considering whether this is a good use case for a Generic Relationship. This will create a polymorphic relationship and is DRY.
Here are the docs
generic relations

How to get user queryset who commented to certain post in Python, Django?

I would like to get a user queryset who commented in certain post.
Assume that there is a post(id=3), and there 8 people commented on it.
I want to notify something to 8 people, so I want to get 8 people's queryset(user objects).
How can I do it?
I have model User, Post, and Comment.
For example,
User.objects.get(comment__Post.objects.filter(id='3'))
like this way. (of course, the upper doesn't work)
Help me!
User model is Django User model.
class Post(models.Model):
post_id = models.CharField('username', max_length=150)
...
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
com_id = models.CharField('username', max_length=150)
....
Ideally your models should look like the following
class Post(models.Model):
posted_by = models.ForeignKey(User, on_delete=models.CASCADE)
...
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
comment = models.CharField(max_length=100)
commented_by = models.ForeignKey(User, on_delete=models.CASCADE)
And your requirement can be achieved through the following query
post = Post.objects.get(id=3)
user_ids = list(Comment.objects.filter(post=post).values_list('user__id', flat=True))
This will return the list of user ids
If you need queryset,
users = User.objects.filter(id__in=user_ids)
In my opinion you should add a OneToOneField to the model Comment and to the model Post that relate to the User model like this:
from django.conf import settings
...
class Post(models.Model):
...
user = models.OneToOneField(settings.User, related_name="posts")
...
class Comment(models.Model):
...
user = models.OneToOneField(settings.User, related_name"comments")
post = models.ForeignKey(Post, related_name="post_comments")
...
The for your queryset you will be able to do:
User.objects.get(posts__id=3)
for comments in Comment.objects.get(post_comments__id=3):
comments.user # this are the user that commented on a certain post.

Categories

Resources