Django : How to count number of people viewed - python

I'm making a simple BBS application in Django and I want it so that whenever someone sees a post, the number of views on that post (post_view_no) is increased.
At the moment, I face two difficulties:
I need to limit the increase in post_view_no so that one user can only increase it once regardless of how many times the user refreshes/clicks on the post.
I also need to be able to track the users that are not logged in.
Regards to the first issue, it seems pretty easy as long as I create a model called 'View' and check the db but I have a feeling this may be an overkill.
In terms of second issue, all I can think of is using cookies / IP address to track the users but IP is hardly unique and I cannot figure out how to use cookies
I believe this is a common feature on forum/bbs solutions but google search only turned up with plugins or 'dumb' solutions that increase the view each time the post is viewed.
What would be the best way to go about this?

I think you can do both things via cookies. For example, when user visits a page, you can
Check if they have “viewed_post_%s” (where %s is post ID) key set in their session.
If they have, do nothing. If they don't, increase view_count numeric field of your corresponding Post object by one, and set the key (cookie) “viewed_post_%s” in their session (so that it won't count in future).
This would work with both anonymous and registered users, however by clearing cookies or setting up browser to reject them user can game the view count.
Now using cookies (sessions) with Django is quite easy: to set a value for current user, you just invoke something like
request.session['viewed_post_%s' % post.id] = True
in your view, and done. (Check the docs, and especially examples.)
Disclaimer: this is off the top of my head, I haven't done this personally, usually when there's a need to do some page view / activity tracking (so that you see what drives more traffic to your website, when users are more active, etc.) then there's a point in using a specialized system (e.g., Google Analytics, StatsD). But for some specific use case, or as an exercise, this should work.

Just to offer a secondary solution, which I think would work but is also prone to gaming (if coming by proxy or different devices). I haven't tried this either but I think it should work and wouldn't require to think about cookies, plus you aggregate some extra data which is noice.
I would make a model called TrackedPosts.
class TrackedPosts(models.Model):
post = models.ForeignKey(Post)
ip = models.CharField(max_length=16) #only accounting for ipv4
user = models.ForeignKey(User) #if you want to track logged in or anonymous
Then when you view a post, you would take the requests ip.
def my_post_view(request, post_id):
#you could check for logged in users as well.
tracked_post, created = TrackedPost.objects.get_or_create(post__pk=id, ip=request.ip, user=request.user) #note, not actual api
if created:
tracked_post.post.count += 1
tracked_post.post.save()
return render_to_response('')

Related

django-ratelimit stack keys. Not the intended behaviour

I think my understanding of django-ratelimit is incorrect. I am using v3.0.0 but v2.0 produces the same results.
Lets say I have this code:
#ratelimit(key='post:username', rate='5/h', block=True)
#ratelimit(key='post:tenant', rate='5/h', block=True)
#csrf_exempt
def index(request):
print(request.POST["username"])
print(request.POST["tenant"])
print("")
return HttpResponse('hallo', content_type='text/plain', status=200)
Let's say tenant A submits username "Antwon" 6 times, then tenant A will be blocked for 1 hour, which is good. But, lets say tenant B also has a user "Antwon", then that user for tenant B will not be able to log in.
I would assume that Antwon for tenant B should still be able to log in, otherwise tenant A can DOS other tenants?
Is this intended behavior or is my implementation incorrect?
My understanding is incorrect. Here is a response from the creator of django-ratelimit:
Hi there! I think there might be some confusion about how multiple ratelimits interact, and how the cache keys get set/rotated.
Multiple limits are ORed together—that is, if you fail any of them,
you fail—but order matters. In this case, the outer limit,
post:username, is tested first, then the tenant is tested. If the user
succeeds, it counts as an attempt, regardless of what happens later.
That's why the user keeps accruing attempts. If you want tenant to
count first, you could re-order the decorators. However...
If you want a single limit to the (username, tenant) pair, you could
combine the two fields with custom logic, by creating a callable key
that returned some combination of tenant and username (maybe
{tenant}\u04{username} or hash them together).
In terms of locking out tenants, there are a couple of things:
First, ratelimit uses a staggered fixed-window strategy, instead of a
sliding windows. So based on the rate, the period, the key value, and
the group, there'll be some calculated window. For example, for 1
hour, 11:03:29 will be the reset time, and the next reset time for
that same combination will be 12:03:29, then 13:03:29, etc... The
downside is that you if you had a limit of 100/h, you could do 200
requests in a short span around the reset point. The upsides are that
the calculation is share-nothing and can be done independently, the
window reset even if you accidentally try again a little too early.
Second, yes if you're doing hard blocks on user-supplied data like
username (instead of e.g. using the authenticated user) it creates a
denial-of-service vector. Where possible, another option is to use
block=False, and do something like require a captcha rather than fully
blocking.

Posting data to database through a "workflow" (Ex: on field changed to 20, create new record)

I'm looking to post new records on a user triggered basis (i.e. workflow). I've spent the last couple of days reasearching the best way to approach this and so far I've come up with the following ideas:
(1) Utilize Django signals to check for conditions on a field change, and then post data originating from my Django app.
(2) Utilize JS/AJAX on the front-end to post data to the app based upon a user changing certain fields.
(3) Utilize a prebuilt workflow app like http://viewflow.io/, again based upon changes triggers by users.
Of the three above options, is there a best practice? Are there any other options I'm not considering for how to take this workflow based approach to post new records?
The second approach of monitoring the changes in the front end and then calling a backend view to update go database would be a better approach because processing on the backend or any other site would put the processing on the server which would slow down the site whereas second approach is more of a client side solution thereby keeping server relieved.
I do not think there will be a data loss, you are just trying to monitor a change, as soon as it changes your view will update the database, you can also use cookies or sessions to keep appending values as a list and update the database when site closes. Also django gives https errors you could put proper try and except conditions in that case as well. Anyways cookies would be a good approach I think
For anyone that finds this post I ended up deciding to take the Signals route. Essentially I'm utilizing Signals to track when users change a fields, and based on the field that changes I'm performing certain actions on the database.
For testing purposes this has been working well. When I reach production with this project I'll try to update this post with any challenges I run into.
Example:
#receiver(pre_save, sender=subTaskChecklist)
def do_something_if_changed(sender, instance, **kwargs):
try:
obj = sender.objects.get(pk=instance.pk) #define obj as "old" before change values
except sender.DoesNotExist:
pass
else:
previous_Value = obj.FieldToTrack
new_Value = instance.FieldToTrack #instance represents the "new" after change object
DoSomethingWithChangedField(new_Value)

Lock the system

I am writing a mini-CRM system that two users can login at the same time and they can answer received messages. However, the problem is that they might response the same message because messages can only disappear when they click "Response" button. Is there any suggestion to me to lock the system?
This sounds like a great case for an 'optimistic locking' approach. Here are two methods I've used with much success. Often, I combine the two methods to ensure no data is lost by mis-matched object instances on POSTs.
The easy way: Add a version field to your model. On POST, check the POSTed version number vs. the object's version number. If they don't match, raise a validation error. If they do match, increment the version by 1.
More elegant approach: Django's Generic Relations (part of the content types framework). A table which stores the content_type and object_id of the object that's locked, along with the user who 'owns' that lock. Check this lock on GET requests, and disable POSTing if it's 'locked' by another user. 'Release' the lock on a page unload, session end, or browser exit. You can get very creative with this approach.
Add some boolean field (answered, is_answered.. etc) and check on every "Response" click if it answered.
Hope it will help.

Django: two sessions in the same browser

I have a Django webapp that allows users to collaborate. Each user is given a link with a unique code they click to go to my site. On the first page visit, I store this unique code in request.session, and then on subsequent page visits I retrieve it to identify the user's record in the DB. I also store various other stuff about the user and their session in request.session.
I would like to allow two sessions to occur in different windows/tabs of the same browser. This is to make testing easier. My colleagues spend a lot of time testing multiple users using the site simultaneously. So far I have been instructing them to use different browsers or different browser profiles, so that the session cookie is not shared. But they always forget this instruction (or do it wrong) and end up confused when the app doesn't work as expected.
My idea is to put the user's unique code (called user_id) in each URL, and then subdivide request.session into multiple dictionaries, so my class-based view would have this:
def dispatch(...):
user_id = kwargs['user_id']
self.request_session = self.request.session[user_id]
Then use this variable self.request_session as I usually would:
self.request_session['time_started'] = now
...
And then before returning my response, assign it back:
self.request.session[user_id] = self.request_session
I think this should be fine, since own code would keep the two sessions isolated in 2 separate dictionaries, but maybe it would break down if Django (or even a 3rd party app) stores something in request.session. Wondering if anyone has a recommendation for another way to handle this.
Modern browsers (e.g. Firefox, Chromium, Brave, …) have the ability to open a page without reference to existing history, cookies, etc.
In Firefox this is termed “Private Browsing”; you open a “Private Window”.
In Brave this is termed a “Private Tab”.
In Chromium this is termed “Incognito Mode”.
By using any of these, you can make a new “session” that will not present as any other logged-in session. This should allow you to have an arbitrary number of sessions at once.

Server side form validation and POST data

I have a user input form here:
http://www.7bks.com/create (Google login required)
When you first create a list you are asked to create a public username. Unfortuantely currently there is no constraint to make this unique. I'm working on the code to enforce unique usernames at the moment and would like to know the best way to do it.
Tech details: appengine, python, webapp framework
What I'm planning is something like this:
first the /create form posts the data to /inputlist/ (this is the same as currently happens)
/inputlist/ queries the datastore for the given username. If it already exists then redirect back to /create
display the /create page with all the info previously but with an additional error message of "this username is already taken"
My question is:
Is this the best way of handling server side validation?
What's the best way of storing the list details while I verify and modify the username?
As I see it I have 3 options to store the list details but I'm not sure which is "best":
Store the list details in the session cookie (I am using GAEsessions for cookies)
Define a separate POST class for /create and post the list data back from /inputlist/ to the /create page (currently /create only has a GET class)
Store the list in the datastore, even though the username is non-unique.
Thank you very much for your help :)
I'm pretty new to python and coding in general so if I've missed something obvious my apologies.
Tom
PS - I'm sure I can eventually figure it out but I can't find any documentation on POSTing data using the webapp appengine framework which I'd need in order to do solution 2 above :s maybe you could point me in the right direction for that too? Thanks!
PPS - It's a little out of date now but you can see roughly how the /create and /inputlist/ code works at the moment here: 7bks.com Gist
I would use Ajax to do an initial validation. For example as soon as the user name input box loses focus I would in the background send a question to the server asking if the user name is free, and clearly signal the result of that to the user.
Having form validation done through ajax is a real user experience delight for the user if done correctly.
Of course before any of the data was saved I would definitely redo the validation server side to avoid request spoofing.
jQuery has a nice form validation plugin if you are interested. http://docs.jquery.com/Plugins/validation.
In my career, I've never gotten around having to validate server side as well as client side though.
About the storing of the list (before you persist it to the datastore). If you use ajax to validate the user name you could keep the other fields disabled until a valid user name is filled in. Don't allow the form to be posted with an invalid user name!
That would perhaps solve your problem for most cases. There is the remote possibility that someone else steals the user name while your first user is still filling in his list of books. If you want to solve that problem I suggest simply displaying the list as you got it from the request from the user. He just sent it to you, you don't have to save it anywhere else.
Can you use the django form validation functionality (which I think should just abstract all this away from you):
http://code.google.com/appengine/articles/djangoforms.html
Search in that page for "adding an item" - it handles errors automatically (which I think could include non-unique username).
Warning: also a beginner... :)

Categories