I want to have a page counter that displays the number of visitors who have viewed a particular page on my site. Is it possible to do this using Django?
There's a Django app for that problem called django-hitcount. It's easy to use, and reusable in any of your projects.
A "page counter" is what? A persistent piece of data which gets updated by view functions and displayed by a template.
As you are no doubt already aware, all Django things have the following parts.
Model
View Function
Template
Model
If you want to keep the page counter in the database, you need a Django model.
class PageCounter( Model ):
You need to put a row into this model. Usually a "fixture" will help do this, since it's one row and you only put it in once when doing a syncdb.
View Function
Then you need to fetch and update the page counter in your view function.
pageCounter= PageCounter.objects.all()[0]
pageCounter.count += 1
pageCounter.save()
Template
Now you need to provide the value to your templates so it can be displayed.
I know this is an old post but occasionally people might have the same question.
If you want to avoid a third party library and prevent the counter being updated at every page refresh you could do the following Mixin (building on S.Lott's 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
It checks if the accessed model has been stored in the session. If it has been stored in the session, it skips incrementing, else it increments the counter and adds the slug of the model to the session preventing increments for page refreshes.
Note: This is a Mixin which you require to add into your view.
Related
Is there a possibility to check that an instance detail is being opened in django admin?
An example with orders illustrates it well - imagine that there is a new order instance in a list display of order instances in django admin. (data come from outside django)
The order model has the following field which is blank when the instance appears in the list;
ProcessingPersonAdminId = models.IntegerField(verbose_name=_('Processing Person Admin Id'), db_column='ProcessingPersonAdminId', blank=True, null=True)
What I need to do - first person (imagine from sales dep.) that clicks on this particular order instance to view its detail is assigned to it. Before it even displays so the field is already filled in with corresponding user data.
I was thinking about signals but nothing is being saved or updated neither in admin nor in models or anywhere else.
I would appreciate any hint on how to approach this task. Thank you in advance.
Solution 1
Do you mean when the changeview page has been opened in the admin app? If so you can do this:
class OrderModelAdmin(admin.ModelAdmin)
def changeform_view(self, request, *args, **kwargs):
user = request.user()
# do something with user
return super().changeform_view(request, *args, **kwargs)
However, is this really what you want to do? Imagine someone accidentally clicks a wrong page. They are automatically assigned. Or maybe someone wants to look at an order without being assigned to it. Quite apart from anything else, this would also go against the principle that a GET request shouldn't change data.
Solution 2
One alternative would be to override the save_model method in your ModelAdmin:
class OrderModelAdmin(admin.ModelAdmin)
def save_model(self, request, obj, form, change):
user = request.user()
obj.id_of_person = user.id
return super().changeform_view(self, request, obj, form, change)
This way, whenever someone uses the admin to make a change to an order, that person is then assigned that order.
Other things to consider
The admin app is not designed to be a production ready, customer facing app. The only people using it should really be devs, who understand the data they are changing. The sales department definitely shouldn't be using the admin app. For this you should write your own views.
My Django project contains a task manager with Projects and Tasks, I have generic list page showing a list of all projects with information on their total tasks:
class IndexView(generic.ListView):
template_name = 'projects/index.html'
context_object_name = 'project_list'
def get_queryset(self):
"""Return 10 projects."""
return Project.objects.order_by('is_complete')[:10]
I would like to display on my list page the total number of added projects and tasks, but I'm unsure how I should go about this. All my current work has been around listing the number of tasks that are included i each project, but now I want a total - should I add this as a new View? For example, I tried adding this to the view above:
def total_projects(self):
return Project.objects.count()
Then calling {{ project_list.total_projects }} on my template, but it doesn't return anything.
Is Views the correct place to do this?
All by current work has been around listing the number of tasks that are included i each project, but now I want a total - should I add this as a new View?
It depends. If you just want to show the total number of projects and tasks with the first 10 completed projects in the database (which is what your get_queryset method does, be careful), I would go and do all of it in the same view (it would be useless to make a new view only to show some numbers, and that isn't the purpose of ListView IMO).
On the other hand, you're calling a class's instance method (total_projects) from a model's instance. That method doesn't exists on the model, and when an attribute/method doesn't exists in an object when calling it in a template, you just get nothing. Based on the previous paragraph, I would set it in the view's context using get_context_data:
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data["total_projects"] = Projects.objects.count()
# I'm assuming you only need the whole number of tasks with no separation
data["total_tasks"] = Task.objects.count()
return data
Finally, you can edit your get_queryset method and make it be an instance attribute (if you want it to be cleaner and you can handle the filtering with no additional code):
class IndexView(generic.ListView):
queryset = Project.objects.order_by('is_complete')[:10]
I believe it's more common to put function definitions in the Model class (Project, from the looks of it), and add the #property tag above the function.
class Project(models.Model):
''' definitions and stuff '''
#property
def total_projects(self): # etc...
As for your specific case, you could forego the function altogether and just use {{ project_list.count }} or {{ project_list|length }} in your template.
A note about count vs length from the docs:
A count() call performs a SELECT COUNT(*) behind the scenes, so you
should always use count() rather than loading all of the record into
Python objects and calling len() on the result (unless you need to
load the objects into memory anyway, in which case len() will be
faster).
Note that if you want the number of items in a QuerySet and are also
retrieving model instances from it (for example, by iterating over
it), it’s probably more efficient to use len(queryset) which won’t
cause an extra database query like count() would.
So use the correct one for your usage.
Also, according to this answer and the below comment from #djangomachine, length may not always return the same number of records as count. If accuracy is important it may be better to use count regardless of the above case.
I am currently writing a wrapped django app for the Google Calendar API. (My plan is to make it open source, if I ever finish it properly.)
The django site's users can be added as guests to the events, and this action is to be syncronized to google calendar. Here are the relevant code pieces:
class Event(models.Model):
def _convert_to_gcal_data(self):
# removed some code here ...
# and the interesting bit is
for attendee in self.attendee_set.all():
data['attendees'].append({
'displayName': attendee.user.name,
'email': attendee.user.email,
'responseStatus': attendee.response_status,
})
return data
def update_to_gcal(self):
"""
Updates the remote event db from local data
:return:
"""
service = cal_utils.get_service(settings.GOOGLE_CAL_USER, settings.GOOGLE_CAL_CERT)
data = self._convert_to_gcal_data()
return service.events().update(calendarId=self.calendar.cal_id, eventId=self.ev_id, sendNotifications=False, body=data).execute()
def register_user(self, user):
self.attendee_set.create(user=user, response_status='accepted')
self.update_to_gcal()
Despite the create method creating the user in attendee_set, the call to attendee_set.all() in _convert_to_gcal_data returns an empty queryset. I guess the create did not commit yet.
How can I work around this behaviour of django?
Update
Checking my tests more carefully, I've found that the described behaviour is present only if I call the register_user method from the view, not when I call it directly as I do in my tests.
The problem was in the view. I was doing a prefetch related on assignee_set, and this caused it not to be reevaluated after the assignee set has changed.
Removing prefetch related from the view code solved the issue.
I'm a newbie with django and working on a project where I need to display registered user's last visited pages on their profile page. I have achieved that within my extended user class by adding a new many2many field to my main object which I want to keep history for. In my view, whenever a member makes a request I add the object to member's history. But this doesn't give me the result that I want. Items are not ordered and if user is not logged in it gives User DoesNotExist error. I know there is a better way then this but I could't find it. Probably I'm not on the correct way. I appreciate any help or ideas.
class myObjectView(View):
model = myObject
template_name = 'app/myobject_detail.html'
def get(self, request, **kwargs):
cat = Category.objects.all()
sec = Section.objects.all()
self.item = myObject.objects.get(slug = self.kwargs[u'slug'])
user = User.objects.get(username=request.user.username)
if user.is_authenticated():
if self.item in user.member.history.all():
user.member.history.remove(self.item)
user.member.history.add(self.item)
user.save()
else:
user.member.history.add(self.item)
user.save()
Your approach has drawbacks but is not a bad one if you need long term persistance.
You could easily add an ordering field in your m2m through table (look at docs) to add some sense of ordering. You could also order your m2m through table by its PK, as large PK values would mean newer entries given your current code of removing items and adding them again.
ordered_item_history = (user.member.history.through.objects
.filter(user=user, myObject=self.item)
.order_by('pk').values_list('myObject', flat=True))
That said the easiest way to do something like this is in the session.
request.session.setdefault('history', []).append(myObj)
request.session.modified = True
Now in any view, you can access this ever-growing list of object history via request.session['history']. Modify as necessary to eliminate duplicates.
I'd like to create a confirmation page for selected objects before a change is made to them (outside the admin). The objects can be of different models (but only one model a time).
This is much like what is done in administration before deletion. But the admin code is complex and I haven't grasped how it is done there.
First I have severall forms that filter the objects differently and then I pass the queryset to the action / confirmation page. I have created a form factory so that I can define different querysets depending on model (as seen in another similiar question here at Stackoverflow):
def action_factory(queryset):
''' Form factory that returns a form that allows user to change status on commissions (sale, lead or click)
'''
class _ActionForm(forms.Form):
items = forms.ModelMultipleChoiceField(queryset = queryset, widget=forms.HiddenInput())
actions = forms.ChoiceField(choices=(('A', 'Approve'), ('D' ,'Deny'), ('W' ,'Under review'), ('C' ,'Closed')))
return _ActionForm
Which I use in my view:
context['form']=action_factory(queryset)()
The problem is that the items field wont be displayed at all in the html-code when it is hidden. When I remove the HiddenInput widget it displays the form correctly.
I don't want to display the choice field since there can be thousands of objects. All I want to have is something like "Do you want to change the status of 1000 objects" and a popdown and a submit button. A simple enough problem it seems, but I can't get it to work.
If someone has a solution to my current attempt I would be glad to hear how they have done it. Even better would be if there is a cleaner and better solution.
I used the wrong widget. It should be MultipleHiddenInput not HiddenInput.