Django ManyToMany field filter - python

So I have this system where my Post object has a ManyToMany field and it's called Saves. So like for example on Reddit you can save a post. So I got it working and users can save posts, and it adds them to the ManyToMany field. However, I want to filter out these posts and only show the posts where said user is in the ManyToMany field.
Here is my models.py
class Post(models.Model):
author = models.ForeignKey(User,related_name='posts',on_delete=models.CASCADE)
saves = models.ManyToManyField(User,blank=True,related_name='post_saves')
I have the saves field connected to the User model Django provides.
And here is my views.py
class PostSaveRedirect(RedirectView):
def get_redirect_url(self,*args,**kwargs):
pk = self.kwargs.get("pk")
slug = self.kwargs.get("slug")
obj = get_object_or_404(Post,pk=pk,slug=slug)
url_ = obj.get_absolute_url()
user = self.request.user
if user.is_authenticated:
if user in obj.saves.all():
obj.saves.remove(user)
else:
obj.saves.add(user)
return url_
So this is all working fine, it adds the user to the ManyToMany field, but now I want to know how I can filter out posts and only display ones where the user is in the ManyToMany field.
Here is my saved posts view.
class PostSaveListView(ListView):
model = Post
template_name = 'mainapp/post_saved.html'
paginate_by = 10
queryset = models.Post.objects.all()
def get(self,request):
posts = Post.objects.all()
return render(request, self.template_name)
def get_queryset(self):
return Post.objects.filter().order_by('-published_date')
So with Post.objects.all(), how can I change it so it will filter to my needs? This is a similar queryset for a user post list view I have
I have been Googling and reading up the docs and other articles but have not found anything that has been able to show me how to filter a ManyToMany field. Any help would be much appreciated

edit your model like this:
class PostSaveListView(ListView):
model = Post
template_name = 'mainapp/post_saved.html'
paginate_by = 10
-
def get(self,request):
posts = Post.objects.all()
return render(request, self.template_name)
def get_queryset(self):
object_list = Post.objects.filter(saves__in=[self.request.user]).order_by('-published_date').distinct()
return object_list

The easiest and quickest option for you would be to use the filtering option in like this:
def get_queryset(self):
return Post.objects.filter(saves__in=[self.request.user]).order_by('-published_date')
Please notice the list inclusion for the user object, as that option only filters from lists.
You may consider also adding .distinct() call to the filter also to avoid repetition of objects.

Related

ManyToMany Relationship between two models in Django

I am trying to build a website that users can add the courses they are taking. I want to know how should I add the ManyToMany relationship. Such that we can get all users in a course based on the course code or instructor or any field. And we can also get the courses user is enrolled in. Currently, my Database structure is:
class Course(models.Model):
course_code = models.CharField(max_length=20)
course_university = models.CharField(max_length=100)
course_instructor = models.CharField(max_length=100)
course_year = models.IntegerField(('year'), validators=[MinValueValidator(1984), max_value_current_year])
def __str__(self):
return self.course_code
and my user model:
class Profile(AbstractUser):
bio = models.TextField()
image = models.ImageField(default='defaults/user/default_u_i.png',
courses = models.ManyToManyField('home.Course',related_name='courses')
def __str__(self):
return self.username
I was wondering should ManyToMany relationship be in User model or the course model? Or will it make any difference at all?
EDIT: For adding course to post object now I am using this view but it seems to not work:
#login_required
def course_add(request):
if request.method == "POST":
form = CourseForm(request.POST or none)
if form.is_valid():
course = form.save()
request.user.add(course)
else:
form = CourseForm
context = {
'form':form
}
return render(request,'home/courses/course_add.html', context)
For a relational databases, the model where you define the ManyToManyField does not matter. Django will create an extra table with two ForeignKeys to the two models that are linked by the ManyToManyField.
The related managers that are added, etc. is all Django logic. Behind the curtains, it will query the table in the middle.
You however need to fix the related_name=… parameter [Django-doc]. The related_name specifies the name of the relation in reverse so from Course to Profile in this case. It thus should be something like 'profiles':
class Profile(AbstractUser):
bio = models.TextField()
image = models.ImageField(default='defaults/user/default_u_i.png',
courses = models.ManyToManyField('home.Course', related_name='profiles')
def __str__(self):
return self.username
You thus can obtain the people that particiate in a Course object with:
mycourse.profiles.all()
and you can access the courses in which a Profile is enrolled with:
myprofile.courses.all()
For more information, see the Many-to-many relationships section of the documentation.
You can add a course to the courses of a user with:
#login_required
def course_add(request):
if request.method == 'POST':
form = CourseForm(request.POST)
if form.is_valid():
course = form.save()
request.user.courses.add(course)
else:
form = CourseForm()
context = {
'form': form
}
return render(request,'home/courses/course_add.html', context)
You don't need to add the related name. Default is "courses_set" in your case.
Here is excerpt from: https://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects
Following relationships “backward” If a model has a ForeignKey,
instances of the foreign-key model will have access to a Manager that
returns all instances of the first model. By default, this Manager is
named FOO_set, where FOO is the source model name, lowercased. This
Manager returns QuerySets, which can be filtered and manipulated as
described in the “Retrieving objects” section above.

Modify field format in listview Django

Is there any good way that I can make modifications to individual fields of data in a list view? For example, I have a blog post model that looks like
class Blog(models.Model):
create_time = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=250)
document = models.TextField(blank=True)
and my list view looks like this:
class BlogPostListView(ListView):
model = Blog
template_name = 'blog/posts.html'
context_object_name = 'posts'
ordering = ['-create_time']
Obviously I want to display a list of blog posts in a table format, and I want to modify how the create_time looks (currently showing 'March 7, 2020, 10:16 a.m.', but I only want 2020-3-7 10:16); and the post document can be too long to display in a cell, I want to truncate it into only 150 words.
so what are the better ways to achieve this in a listview view?
(I read some sort-of similar questions in StackOverflow, but it is either done in the template such as reformating the time which does not solve my second requirement, or too vague to understand).
If you want to customise the representation between model and view. A faster way to do that is to use serializer and RetrieveAPIView of DRF. Check out installation link. Then, you can make the use of serializer to customise representations of model's fields.
Here is pseudo code:
from rest_framework import serializers
from rest_framework.generics import RetrieveAPIView
class BlogSerializer(serializers.ModelSerializer)
def get_create_time(self, blog):
return self.create_time..strftime("%m/%d/%Y, %H:%M)
def get_document(self, blog):
return self.document[:151]
class BlogPostListView(RetrieveAPIView):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
template_name = 'blog/posts.html'
As Chouvic suggested that I should use ModelSerializer, I managed to get it work in the listView. Here is my solution:
serializer:
class BlogListSerializer(serializers.ModelSerializer):
create_time_str = serializers.SerializerMethodField()
short_document_str = serializers.SerializerMethodField()
class Meta:
model = Blog
fields = '__all__'
def get_create_time_str(self, obj):
return obj.create_time.strftime("%Y/%m/%d")
def get_short_document_str(self, obj):
return obj.document[0:150]
as for the ListView:
class BlogListView(ListView):
model = Blog
template_name = 'blog/posts.html'
context_object_name = 'posts'
ordering = ['-create_time']
queryset = Blog.objects.all()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
posts = context['posts']
mod_posts = BlogListSerializer(posts, many=True)
context['posts'] = mod_posts.data
return context
BlogListSerializer(posts, many=True) takes in posts (a list of post) and return a new list of post each with the new 'attributes' of create_time_str and short_document_str.
In the template, I just need to refer it as <td>{{ post.create_time_str }}</td>.

Filter "get_context_data" using multiple data on a ListView

In my ListView I would like to filter the data by the current user logged from the context_data in :
views.py
class DashboardListView(LoginRequiredMixin,ListView):
model = Links
template_name = 'dashboard/home.html'
context_object_name ='links_list'
paginate_by = 15
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['dashboard_list']= Dashboard.objects.filter()[:15]
context['todo_list']= Todo.objects.all().order_by('-pk')[:15]
context['todo_complete']= Todo.objects.all().count()
context['PasswordUsername_list']= PasswordUsername.objects.all()
return context
I tried to override with a query_set but it does work only for the links model
Well, you need to filter all those queries.
context['dashboard_list']= Dashboard.objects.filter(user=self.request.user)[:15]
context['todo_list']= Todo.objects.filter(user=self.request.user).order_by('-pk')[:15]
etc - assuming your models all have a user FK field pointing to the User model.
See the answer below it's working but do not forget to add :
def get_queryset(self):
return self.model.objects.filter(user=self.request.user)
To your ListView otherwise the first model will still be shown.

How can I code the query set in order that the users can see only their own post in profile section

I'm working on a project by using Django 2.06. I have multiple users and they can post and other people can read their post which is very normal. At the user profile page User should see their own post.
this is where I'm stuck, How i can code the queryset then at the profile section user can see only their own post.
code demo
class ProfileView(ListView):
template_name = "profile.html"
queryset = QuickWord.objects.filter()
context_object_name = 'quickword'
def get_context_data(self, **Kwargs):
context = super(ProfileView, self).get_context_data(**Kwargs)
context['addproduct'] = AddProduct.objects.filter()
context['article_view'] = Article.objects.filter()
context['edit'] = Profile.objects.all().last()
return context
I know that I have to use filter value but do not know how to do that.
Thanks
Assuming your have an author field in your Post model, You should do something like this in your views.py
def profile(request):
user_posts = Post.objects.filter(author=request.user)
return render(request, 'path/to/profile.html', {'posts': user_posts})
Of course the above view requires the user to be logged in.

How to access profile object(profile id) in Django template?

I have a view that I set up that allows me to view all the training for a specific employee profile:
class ManageTrainingView(LoginRequiredMixin, generic.ListView):
login_url = reverse_lazy('users:login')
model = Training
template_name = 'ppm/training.html'
paginate_by = 10
# Get training queryset for specific profile id
def get_queryset(self):
pk = self.kwargs['pk']
return Training.objects.filter(profile_id=pk)
The problem I'm have is that I'm trying to add more training for a specific employee profile using a profile id instead of a user id. In my training.html template this Works
training.html
<li>Add Training</li>
But this doesn't(NoReverseMatch)
<li>Add Training</li>
So my question is what do I need to do in order to add training for a specific employee profile instead of a user? How do I access the profile object that I'm currently on in the template so that profile.id will work?
I found a solution to my problem and I think this is it. If anyone has a better solution please let me know.
What I did was override get_context_data to return the profile id to the template.
def get_context_data(self, **kwargs):
pk = self.kwargs['pk']
context = super(ManageTrainingView, self).get_context_data(**kwargs)
context['profile_id'] = pk
return context
Now I am able to use:
<li>Add Training</li>

Categories