Hit counter for Django blog post - django

In my search to find a way to add hit counter to my blog posts without any third party library I found this answer on StackOverflow.
However as I'm not a Django expert I can't figure out how to use that Mixin with my view.
Here's how my model is defined:
class Post(models.Model):
STATUS_CHOICES = (('draft', 'Draft'), ('published', 'Published'))
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique_for_date='publish')
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')
body = models.TextField()
publish = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
And my views:
def post_list(request):
posts = Post.published.all()
return render(request, 'blog/post/list.html', {'posts': posts})
class PostDetailView(DetailView):
model = Post
context_object_name = 'post'
And here's the mixin provided in that answer:
class BlogPostCounterMixin(object):
def get_context_data(self, **kwargs):
context = super(BlogPostCounterMixin, self).get_context_data(**kwargs)
blog_post_slug = self.kwargs['slug']
if not blog_post_slug in self.request.session:
bp = BlogPost.objects.filter(slug=blog_post_slug).update(counter=+1)
# Insert the slug into the session as the user has seen it
self.request.session[blog_post_slug] = blog_post_slug
return context
I would ask the user provided that answer, but he's inactive for more than 2 years.
Appreciate your help.

you can create a IntegerField called views in your Model
then in your post_detail view you could do this just before return statement
post.views += 1
post.save(update_fields=['views'])
Obviously this solution has the drawback of some views that happen at exactly the same moment being missed.
Edit:
before using a mixin you must first use a class based view instead of a function based view then you can use this mixin and in your case you would want to use a DetailView
https://docs.djangoproject.com/en/3.2/ref/class-based-views/generic-display/#detailview
class BlogPostCounterMixin:
def get_object(self, *args, **kwargs):
# get_object will be available if you use a DetailView
obj = super().get_object(*args, **kwargs):
post_unique_key = 'post_%s' % obj.pk # or whatever unique key is
if not post_unique_key in self.request.session:
obj.views += 1
obj.save(update_fields=['views'])
self.request.session[post_unique_key] = post_unique_key
return obj
and can be used like this
class PostDetailView(BlogPostCounterMixin, DetailView):
model = Post
context_object_name = 'post'

Related

Combine DetailView and UpdateView?

I am new to Django and I need to know how to have DetailView and UpdateView on the same Page.
I have two Models:
class Company(models.Model):
CustomerNo = models.AutoField(primary_key=True)
Company = models.CharField(max_length=200)
Str = models.CharField(max_length=200)
Zip = models.IntegerField()
City = models.CharField(max_length=200)
Name = models.CharField(max_length=200)
Phone = models.IntegerField()
Mobile = models.IntegerField()
Email = models.EmailField(max_length=200)
Web = models.CharField(max_length=200)
Info = models.CharField(max_length=200)
def __str__(self):
return self.Company
class Contact(models.Model):
Contact_Company = models.ForeignKey(Company, on_delete=models.CASCADE)
Contact_Name = models.CharField(max_length=200)
Contact_Phone = models.IntegerField()
Contact_Mobile = models.IntegerField()
Contact_Fax = models.IntegerField()
Contact_E_Mail = models.EmailField()
Contact_Web = models.CharField(max_length=200)
def __str__(self):
return self.Contact_Name
I want to build a page where I can see the company data from the first model and an update form for contacts realeted to the first model.
I enter the page with pk, from the previous page, its a DetailView for the first Model and with additionally context to list the Contact data with a for loop in Template.
I can use UpdateView to get data in the form and save it. but I don't know
how do display the realeted Company on the same page. Is there a way to use DetailView and UpdateView together?
I can use this UpdateView to change the Contact data, but I don't know how to include extra context from the first model to display the address on same page.
The success URL is wrong too.
I need to pass the pk from the first model so I can go back to the right list on previous page.
class ContactUpdate(UpdateView):
model = Contact
form_class = ContactCreateForm
template_name = 'customer/contact_update.html'
def get_success_url(self):
return reverse('customer_list', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(ContactUpdate, self).get_context_data(**kwargs)
return context
Maybe FormMixin is a solution, I used it to Display the Data from
first Model and form for second Model on same Page. But I am really stuck
to realize this with UpdateForm.
I hope you understand my problem, sorry for my english.
Thank you for your help.
Forms.py
from django.forms import ModelForm
from .models import Company
from .models import Contact
from django.forms import HiddenInput
from django import forms
class CompanyCreateForm(ModelForm):
class Meta:
model = Company
fields = '__all__'
class ContactCreateForm(ModelForm):
class Meta:
model = Contact
widgets = {'Contact_Company': forms.HiddenInput()}
fields = [
'Contact_Company',
'Contact_Name',
'Contact_Phone',
'Contact_Mobile',
'Contact_Fax',
'Contact_E_Mail',
'Contact_Web',
You need to add form in the detail view,
class PostDetailView(DetailView):
model = Post #your model name
template_name = 'detail.html' #your template
# here you will add your form
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['contactupdateform'] = ContactCreateForm()
return context
# Add POST method
def post(self, request, slug):
post = get_object_or_404(Post, slug=slug)
form = contactupdateform(request.POST)
if form.is_valid():
# from here you need to change your post request according to your requirement, this is just a demo
obj = form.save(commit=False)
obj.post = post
obj.author = self.request.user #to add the user
obj.save()
return redirect('detail', post.slug) #add your url
Make sure you are adding the POST request correctly, according to your model and url. This is an outline you can refer.
To add the form in the HTML, you need to do this,
{% for form in contactupdateform %}
<-- Add Your CSRF token and form here --!>
{% endfor %}
You can import this (LoginRequiredMixin) and insert in the updateview as an argument as the contact is a user
then try putting this in the models.py file :
def get_absolute_url(self):
return reverse('customer_list', kwargs={'pk': self.pk})
and remove (get_success_url) from views.py
You might need these too in the updateview Class "ContactUpdate"
login_url = '/login/'
redirect_field_name = <-- template path(html) of the page you want to reverse to...
HOPE THIS HELPS...

django rest update_or_create

Background:
I am trying to create a system which allows users to vote on comments written by other users (similar to Reddit). Users have the choice of three vote values: -1, 0, or 1. I have created a POST API (using django rest-framework) that will store a user's vote with respect to a particular comment. If the user has already voted on the given comment, then it will update the existing user’s vote value to the new one.
I drew inspiration from this post:Django RF update_or_create
Problem:
Once a comment has had one user submit a vote on it, Django creates a duplicate comment object with the same ID/primary key whenever another user votes on the same comment. I have taken a screenshot of my admin page where it says I have 3 objects to select but only a single comment. Why is it doing this and how can I prevent it?
Screenshot of my comment admin page
I am new to Django. I suspect I might be doing something wrong when I define my own "create" method in my serializer. I'd appreciate any help. Thanks!
models.py:
Comment model:
class Comment(models.Model):
location_property_category = models.ForeignKey('locations.LocationPropertyCategory',on_delete=models.CASCADE,related_name='comments',null=True)
author = models.ForeignKey('auth.User',on_delete=models.PROTECT,related_name='comments')
location = models.ForeignKey('locations.Location',on_delete=models.CASCADE,related_name='comments')
text = models.TextField()
create_date = models.DateTimeField(default=timezone.now())
published_date = models.DateTimeField(blank=True,null=True)
approved_comment = models.BooleanField(default=False)
objects = CommentManager()
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
def save(self, *args, **kwargs):
if self.approved_comment is True:
self.published_date = timezone.now()
super(Comment, self).save(*args, **kwargs)
def sum_vote(self):
return self.upvotedownvotes.aggregate(Sum('vote')).get('vote__sum') or 0
Vote model:
class UpVoteDownVote(models.Model):
UPVOTE = 1
NEUTRALVOTE = 0
DOWNVOTE = -1
VOTES = (
(UPVOTE, 'Upvote'),
(NEUTRALVOTE, 'Neutralvote'),
(DOWNVOTE, 'Downvote')
)
vote = models.SmallIntegerField(choices=VOTES)
user = models.ForeignKey('auth.User', related_name='upvotedownvotes', on_delete=models.CASCADE)
comment = models.ForeignKey(Comment, related_name='upvotedownvotes', on_delete=models.CASCADE)
date_voted = models.DateTimeField(default=timezone.now())
class Meta:
unique_together = (('user','comment'),)
Comment manager model:
class CommentManager(models.Manager):
def get_queryset(self):
return super(CommentManager, self).get_queryset().order_by('-upvotedownvotes__vote')
serializers.py
Vote serializer:
class UpVoteDownVoteSerializer(serializers.ModelSerializer):
class Meta:
model = UpVoteDownVote
fields = ('vote','comment')
def get_fields(self):
fields = super(UpVoteDownVoteSerializer, self).get_fields()
fields['comment'].queryset = Comment.objects.filter(approved_comment=True)
return fields
def create(self, validated_data):
votedata, created = UpVoteDownVote.objects.update_or_create(
user=validated_data.get('user', None),
comment=validated_data.get('comment', None),
defaults={'vote': validated_data.get('vote', None),
})
return votedata
views.py
class UpVoteDownVoteCreateApiView(generics.CreateAPIView):
serializer_class = UpVoteDownVoteSerializer
permission_classes = [IsAuthenticated]
def perform_create(self,serializer):
serializer.save(user=self.request.user)
Comment app admin.py
class CommentAdmin(admin.ModelAdmin):
readonly_fields = ('id',)
admin.site.register(Comment,CommentAdmin)
Welcome to StackOverflow!
Your problem is in CommentManager:
queryset.order_by('-upvotedownvotes__vote')
This query basically creates LEFT_OUTER_JOIN. So you result looks like:
comment#1 upvote#1
comment#1 upvote#2
comment#1 upvote#3
That is why you are seeing comment#1 3 times.
I believe that you want to use something like this: https://docs.djangoproject.com/en/2.1/topics/db/aggregation/#order-by

Setting form fields in django class based generic view CreateView

I'm using django's CreateView to add images to a book. I pass the book's id to the class based view as a parameter in the url. Form fields such as book and language are not rendered on the template, rather they're obtained with the help of the book's id.
# views.py
class PictureCreateView(CreateView):
model = Upload
fields = "__all__"
book_id = None
def get_initial(self):
initial = super(PictureCreateView, self).get_initial()
initial = initial.copy()
self.book_id = self.kwargs['book_id']
book = Book.objects.get(id=self.book_id)
initial['book'] = book
initial['language'] = language
initial['uploader'] = self.request.user
return initial
# set book_id so it used in the template
def get_context_data(self, **kwargs):
context = super(PictureCreateView, self).get_context_data(**kwargs)
context['book_id'] = self.book_id
return context
def form_valid(self, form, **kwargs):
print('Form is valid')
self.object = form.save()
files = [serialize(self.object)]
data = {'files': files}
response = JSONResponse(data, mimetype=response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return super(PictureCreateView, self).form_valid(form)
def form_invalid(self, form):
print('Form invalid!')
print(form.errors)
data = json.dumps(form.errors)
return HttpResponse(content=data, status=400, content_type='application/json')
# models.py
class Upload(models.Model):
image = models.ImageField(upload_to=get_upload_path, help_text='Image to process')
uploader = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE, related_name='uploader')
language = models.ForeignKey(Language, models.CASCADE)
book = models.ForeignKey(Book, models.CASCADE)
The problem is that I get an error saying the form is invalid, and the fields uploader, book and language are required. How do I resolve this?
The initial data is used to display the defaults when the form is initially displayed. It isn't used when those values are missing from the submitted form data. If fields like book and uploader are set from the URL or logged-in user, then you should leave them out of the form completely, instead of setting them in the initial data. You can then set the values on the instance in the form_valid method before the form is saved.
from django.contrib.auth.mixins import LoginRequiredMixin
class PictureCreateView(LoginRequiredMixin, CreateView):
model = Upload
fields = ['other_field1', 'other_field2', ...] # leave out book, language and uploader
def form_valid(self, form):
self.book_id = self.kwargs['book_id']
book = Book.objects.get(id=self.book_id)
form.instance.book = book
form.instance.language = ????
form.instance.uploader = self.request.user
return super(
The LoginRequiredMixin makes sure that only logged-in users can access the view.
You may want to use get_object_or_404 to handle the case where book_id refers to a book that does not exist.
One thought, initial doesn't fill the model for submission. You need to do that in init
def __init__(self):
super(PictureCreateView, self).__init__()
self.fields['book'] = self.initial['book']
self.fields['uploader'] = self.initial['uploader']
self.fields['language'] = self.initial['book']
Or, if you don't want to set the fields, make sure they are optional in your original model:
class Upload(models.Model):
uploader = models.ForeignKey('uploader', on_delete=models.CASCADE, null=True, blank=True)
book = models.ForeignKey('book', on_delete=models.CASCADE, null=True, blank=True)
language = models.ForeignKey('language', on_delete=models.CASCADE, null=True, blank=True)

Need help getting correct instance for form_valid in a generic view

I can't work out how to get the correct instance for the form_valid part of my generic view.
I am trying to allow a user to post on their project wall(bit like Facebook). I need the post to be related to an individual project(a user can have more than one project). Should the instance be a pk or the project title? Any example code or help would be very appreciated! I struggle understanding how when you create a new post, it knows which project to associate itself with.
views
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
newpost = form.save(commit=False)
form.instance.user = self.request.user
newpost.save()
self.object = newpost
return super(NewPost, self).form_valid(form)
def get_success_url(self):
project_username = self.request.user.username
project_slug = self.object.slug
return reverse('user_project', kwargs={'username':project_username, 'slug': project_slug})
models
class UserProject(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
project_overview = models.CharField(max_length=1000)
project_picture = models.ImageField(upload_to='project_images', blank=True)
date_created = models.DateTimeField(auto_now_add=True)
project_views = models.IntegerField(default=0)
project_likes = models.IntegerField(default=0)
project_followers = models.IntegerField(default=0)
slug = models.SlugField(max_length=100, unique=True) #should this be unique or not?
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(UserProject, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
class ProjectPost(models.Model):
project = models.ForeignKey(UserProject)
title = models.CharField(max_length=100)
post_overview = models.CharField(max_length=1000)
date_created = models.DateTimeField(auto_now_add=True)
post_views = models.IntegerField(default=0)
post_likes = models.IntegerField(default=0)
forms
#form to add project details
class UserProjectForm(forms.ModelForm):
class Meta:
model = UserProject
fields = ('title', 'project_picture', 'project_overview')
#form to create a post
class ProjectPostForm(forms.ModelForm):
class Meta:
model = ProjectPost
fields = ('title', 'post_overview')
Ok, in that case, I would recommend a URL something like
url(r'^(?P<pk>\d+)/post/add/$', views.NewPostCreateView.as_view(), name='...'),
and then a view like
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
self.object = form.save(commit=False)
# Find project by using the 'pk' in the URL
project = get_object_or_404(UserProject, pk=self.kwargs['pk'])
# Then just set the project on the newPost and save()
self.object.project = project
self.object.save()
return super(NewPost, self).form_valid(form)
def get_success_url(self):
# Unchanged ...
I see in your code that you were trying to do something with the user but I don't understand why your Post does not have a user field (you may want to add a created_by) and the UserProject should already have a user set.
I am also assuming the user got to the his/her project first, so you know by definition that the project he is adding a post to is his. If that is not the case, then just change the logic to get the UserProject through a regular query. e.g. maybe with `UserProject.objects.get(user = self.request.user) if there is one project per user (again, just as an example).
Anyway, I am making some assumptions here, but hopefully the main question was how to set the project on the newPost and that is answered in my example.

Foreign key lookups - slug URL with Django generic list view

I have been searching here and reading the documentation and experimenting in python, but I can't find a solution to my particular mess. I did the Django tutorial, but I'm still confused as to how to pass stuff thru the URL in django when starting to use foreign keys, I'm sure it's pretty simple. I'm a new django user and this is my first post. I have the models Playlist, and PlaylistEntry with mixed relationships to user and videos (not posted). I'm trying to show a detail view that uses a slug of a playlist title, to pull out entries in the playlist. Eg. In python, I can do
entries = PlaylistEntry.objects.filter(playlist__slug='django')
which returns all my entries in the playlist 'Django' correctly.
Here are my models...
class Playlist(models.Model):
class Meta:
verbose_name_plural = 'playliztz'
title = models.CharField(blank=True,
help_text=u'Title of playlist',
max_length=64,
)
user = models.ForeignKey('userprofile.UserProfile',
blank=False,
help_text=u'owns this playlist',
null=False, )
slug = models.SlugField(u'slug',
max_length=160,
blank=True,
editable=False
)
created = models.DateTimeField(editable=False)
modified = models.DateTimeField(editable=False)
def __unicode__(self):
return self.title
def get_absolute_url(self):
return reverse('playlist-detail', kwargs={'pk': self.pk})
def save(self):
self.slug = slugify(self.title)
if not self.id:
self.created = datetime.datetime.today()
self.modified = datetime.datetime.today()
super(Playlist,self).save()
class PlaylistEntry(models.Model):
class Meta:
verbose_name_plural = "playlist entries"
video = models.ForeignKey('video.Video',
blank=True,
default=None,
help_text=u'the video title',
null=True, )
playlist = models.ForeignKey('Playlist',
blank=True,
default=None,
help_text=u'Belongs to this playlist',
null=True,)
def __unicode__(self):
return self.video.video_title
My URLS looks like this...
url(r'^playlist/$', PlaylistList.as_view(), name='user_playlists'),
url(r'^playlist/(?P<slug>[0-9A-Za-z-_.//]+)/$', PlaylistDetail.as_view(), name='playlist_entries'),
And my Views.py looks like this...
class PlaylistList(LoggedInMixin, ListView): # shows a list of playlists
template_name = 'userprofile/playlist_list.html'
model = Playlist
context_object_name = 'playlist_list'
def get_queryset(self):
"""Return currently users playlists."""
return Playlist.objects.filter(user__user=self.request.user)
def get_context_data(self, **kwargs):
context = super(PlaylistList, self).get_context_data(**kwargs)
if not self.get_queryset():
context['error'] = "You don't have any playlists yet."
return context
else:
return context
class PlaylistDetail(LoggedInMixin, DetailView):
model = PlaylistEntry
template_name = 'userprofile/playlist_detail.html'
def get_queryset(self):
self.current_playlist = get_object_or_404(Playlist, slug=self.kwargs['slug'])
# CHECK - Prints out correct entries for playlist in slugfield (current_playlist)
self.entries = PlaylistEntry.objects.filter(playlist__title=self.current_playlist.title)
print self.entries
# Should expect to return the same queryset?
return PlaylistEntry.objects.filter(playlist__title=self.current_playlist.title)
def get_context_data(self, **kwargs):
context = super(PlaylistEntry, self).get_context_data(**kwargs)
context['entries'] = PlaylistEntry.objects.all()
return context
self.entries prints the correct entries for this playlist in the Check bit.
In my playlist template I am using a link sending the playlist.slug - the url looks correct like this /user/playlist/this-particular-playlist-slug.
the error is...
Cannot resolve keyword u'slug' into field. Choices are: id, playlist, video
You've made things much more complicated than they need to be.
The model for your detail view should still be Playlist, not PlaylistEntry. The reason you're getting that error is that the slug is on the Playlist model, but you've told the view to filter on PlaylistEntry.
What you actually want to to is to pass the single Playlist identified by the slug into the template. From there, you can easily iterate through the detail objects associated with that playlist via the reverse relation.
So, change that model setting, and drop both get_context_data and get_queryset from the detail view: you don't need them. Then in the template you can simply do:
{% for entry in playlist.playlistentry_set.all %}