I'm new to Django and having some doubts on how to do this. I've installed an APP called Django-voting, https://github.com/jezdez/django-voting/
This APP allow the user to vote on is own objects. I need to deny this but not sure on how to do it. How can I know the owner of an object?
The code that I've to override is this view:
def vote_on_object(request, model, direction, post_vote_redirect=None,
object_id=None, slug=None, slug_field=None, template_name=None,
template_loader=loader, extra_context=None, context_processors=None,
template_object_name='object', allow_xmlhttprequest=False):
"""
Generic object vote function.
The given template will be used to confirm the vote if this view is
fetched using GET; vote registration will only be performed if this
view is POSTed.
If ``allow_xmlhttprequest`` is ``True`` and an XMLHttpRequest is
detected by examining the ``HTTP_X_REQUESTED_WITH`` header, the
``xmlhttp_vote_on_object`` view will be used to process the
request - this makes it trivial to implement voting via
XMLHttpRequest with a fallback for users who don't have JavaScript
enabled.
Templates:``<app_label>/<model_name>_confirm_vote.html``
Context:
object
The object being voted on.
direction
The type of vote which will be registered for the object.
"""
if allow_xmlhttprequest and request.is_ajax():
return xmlhttprequest_vote_on_object(request, model, direction,
object_id=object_id, slug=slug,
slug_field=slug_field)
if extra_context is None:
extra_context = {}
if not request.user.is_authenticated():
return redirect_to_login(request.path)
try:
vote = dict(VOTE_DIRECTIONS)[direction]
except KeyError:
raise AttributeError("'%s' is not a valid vote type." % direction)
# Look up the object to be voted on
lookup_kwargs = {}
if object_id:
lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
elif slug and slug_field:
lookup_kwargs['%s__exact' % slug_field] = slug
else:
raise AttributeError('Generic vote view must be called with either '
'object_id or slug and slug_field.')
try:
obj = model._default_manager.get(**lookup_kwargs)
except ObjectDoesNotExist:
raise Http404('No %s found for %s.' %
(model._meta.app_label, lookup_kwargs))
if request.method == 'POST':
if post_vote_redirect is not None:
next = post_vote_redirect
elif 'next' in request.REQUEST:
next = request.REQUEST['next']
elif hasattr(obj, 'get_absolute_url'):
if callable(getattr(obj, 'get_absolute_url')):
next = obj.get_absolute_url()
else:
next = obj.get_absolute_url
else:
raise AttributeError('Generic vote view must be called with either '
'post_vote_redirect, a "next" parameter in '
'the request, or the object being voted on '
'must define a get_absolute_url method or '
'property.')
Vote.objects.record_vote(obj, request.user, vote)
return HttpResponseRedirect(next)
else:
if not template_name:
template_name = '%s/%s_confirm_vote.html' % (
model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
template_object_name: obj,
'direction': direction,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
response = HttpResponse(t.render(c))
return response
I think I've to add some kind of verification here,
Vote.objects.record_vote(obj, request.user, vote)
Any clues on this subject?
Best Regards,
This app doesn't manage whether a user is owner or not of any object where he can make a vote, so you need to keep this control in each model who represents an entity able to be voted. For example, if you have a model A and you wish to know which user is owner of A you should have a user relation with the model A for tracking model owner users. We can represent this through an example:
from django.contrib.auth.models import User
from django.db import models
from django.contrib import messages
class A(models.Model):
owner_user = models.ForeignKey(User)
so in any place on your code (in a view or in a verification method) you can do something like this:
# I will call `user_who_votes` the user who is making the action of voting
# if you are in a view or have a `request` instance, you can access to its instance,
# as surely you already know, with `user_who_votes = request.user`, always checking
# this user is authenticated (`if request.user.is_authenticated():`).
try:
# Checking if the user who is voting is `A`'s owner,
# if he is, so you can register a message and show it
# to the user when you estimate (it is only an idea,
# maybe you can have a better option, of course).
a = A.objects.get(owner_user=user_who_votes)
messages.add_message(request, messages.ERROR, 'You can not vote on your own entities.'))
except A.DoesNotexist:
# In this point you are sure at all that `user_who_votes`
# isn't the owner of `A`, so this user can vote.
Vote.objects.record_vote(a, user_who_votes, vote)
Hope this can help you.
Related
I want to send a login link to the users.
I know there are some OneTimePassword apps out there with thousands of features. But I just want some easy and barebon way to login user via login link.
My question is if this is a correct way to go about this. Like best practice and DRY code.
So I've set up a table that stores three rows.
1. 'user' The user
2. 'autogeneratedkey' A autogenerated key
3. 'created_at' A Timestamp
When they login, the'll be sent a mail containing a login link valid for nn minutes.
So the login would be something like
https://example.net/login/?username=USERNAME&autogeneratedkey=KEY
The tricky part for me is to figure out a good way to check this and log in the user.
I'm just guessing here. But would this be a good approach?
class login(generic.CreateView):
def get(self, request, *args, **kwargs):
try:
autgeneratedkey = self.request.GET.get('autgeneratedkey', '')
username = self.request.GET.get('username', '')
obj_key = Login.objects.filter(autgeneratedkey=autgeneratedkey)[0]
obj_user = Login.objects.filter(userusername=username)[0]
try:
if obj_user == obj_key: #Compare the objects if same
if datetime.datetime.now() < (obj_key.created_at + datetime.timedelta(minutes=10)): #Check so the key is not older than 10min
u = CustomUser.objects.get(pk=obj_user.user_id)
login(request, u)
Login.objects.filter(autgeneratedkey=autgeneratedkey).delete()
else:
return login_fail
else:
return login_fail
except:
return login_fail
return redirect('index')
def login_fail(self, request, *args, **kwargs):
return render(request, 'login/invalid_login.html')
It feels sloppy to call the same post using first the autogeneratedkey then using the username. Also stacking if-else feels tacky.
I would not send the username in the get request. Just send an autogenerated key.
http://example.com/login?key=random-long-string
Then this db schema (it's a new table because I don't know if Login is already being used.
LoginKey ( id [PK], user [FK(CustomUser)], key [Unique], expiry )
When a user provides an email, you create a new LoginKey.
Then do something like this:
def get(self, request, *args, **kwargs):
key = request.GET.get('key', '')
if not key:
return login_fail
login_key = LoginKey.objects.get(key=key)
if login_key is None or datetime.datetime.now() > login_key.expiry:
return login_fail
u = login_key.user
login(request, u)
login_key.delete()
return redirect('index')
Probably you can optimize the code like this:
First assuming you have relationship between User and Login Model like this:
class Login(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
Then you can use a view like this:
class LoginView(generic.View):
def get(self, request, *args, **kwargs):
try:
autgeneratedkey = self.request.GET.get('autgeneratedkey', '')
username = self.request.GET.get('username', '')
user = CustomUser.objects.get(login__autgeneratedkey=autgeneratedkey, username=username, login__created_at__gte=datetime.now()-datetime.timedelta(minutes=10))
login(request, user)
user.login_set.all().delete() # delete all login objects
except CustomUser.DoesNotExist:
return login_fail
return redirect('index')
Just another thing, it is not a good practice to use GET method where the database is updated. GET methods should be idempotent. Its better to use a post method here. Just allow user to click the link(which will be handled by a different template view), then from that template, use ajax to make a POST request to this view.
I have a survey app - you create a Survey and it saves the Response. It's registered in Django Admin. I can see the Survey and submit a Response. When I click Response in Admin, I get the following error:
ValueError at /admin/django_survey/response/
Cannot query "response 5f895af5999c49929a522316a5108aa0": Must be "User" instance.
So I checked the SQL database and for django_survey_response I can see that there is a response, but the column user_id is NULL.
I suspected that there's an issue with my Views and/or Forms and I'm not saving the logged in User's details, so I've tried to address that.
However, now I get
NameError at /survey/1/
global name 'user' is not defined
How do I resolve this? I want the form to save Response with the logged in user's ID.
The Traceback:
django_survey\views.py
def SurveyDetail(request, id):
survey = Survey.objects.get(id=id)
category_items = Category.objects.filter(survey=survey)
categories = [c.name for c in category_items]
print 'categories for this survey:'
print categories
if request.method == 'POST':
form = ResponseForm(request.POST, survey=survey) <.........................
if form.is_valid():
response = form.save()
return HttpResponseRedirect("/confirm/%s" % response.interview_uuid)
else:
form = ResponseForm(survey=survey)
print form
django_survey\forms.py
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
self.survey = survey
self.user = user <.........................
super(ResponseForm, self).__init__(*args, **kwargs)
self.uuid = random_uuid = uuid.uuid4().hex
# add a field for each survey question, corresponding to the question
# type as appropriate.
data = kwargs.get('data')
It might be worth noting that previously, instead of user, the model's field was called interviewee. I changed this and ran migrations again.
I am also using userena.
The error message in this instance is python trying to tell you that you are attempting to access a variable user that has not been defined in the scope of your method.
Let's look at the first few lines of the __init__() method:
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
self.survey = survey
self.user = user
We can see where the survey variable is defined: survey = kwargs.pop('survey'). It is passed into the form as a keyword argument and extracted in the forms __init__. However underneath you attempt to do the same thing with user but haven't actually defined it above. The correct code would look like:
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
user = kwargs.pop('user')
self.survey = survey
self.user = user
However, this still won't work because we aren't passing the user variable to the form via kwargs. To do that we pass it in when we initialise the form in your views.py. What isn't clear is what user object you are expecting to pass in. the request.user? or does the Survey object have a user attribute? in which case you would not need to pass user in and would just use survey.user etc.
django_survey\views.py
def SurveyDetail(request, id):
survey = Survey.objects.get(id=id)
category_items = Category.objects.filter(survey=survey)
categories = [c.name for c in category_items]
print 'categories for this survey:'
print categories
if request.method == 'POST':
form = ResponseForm(request.POST, survey=survey, user=request.user)
if form.is_valid():
response = form.save()
return HttpResponseRedirect("/confirm/%s" % response.interview_uuid)
else:
form = ResponseForm(survey=survey, user=request.user)
print form
In your view when you initialize your form you need to pass it the user (current user in this case)? similar to this form = ResponseForm(request.POST, survey=survey, user=request.user). Then in the __init__ of your form pop the user object user = kwargs.pop('user'). I believe that will resolve your issue.
In my django project, I have a view that displays details of an event. In that view there is a link to another view that contains the form to register people to that event.
If the maximum number of participants is reached and someone still tries to register, my view throws an exception. And returns from the formular-site back to the event-site. But the event-site is not fully loaded, I think the queries are no performed.
I don't know how to write the return function.
Additionally, is there a syntax error in my exception? When I create new instances of registration in the shell, I can save as many as I want.
def validate_category_full(category_id):
cat = Category.objects.get(id=category_id)
regs = cat.registration_set.all()
if len(regs) >= cat.max_people:
raise ValidationError('Category is already full.')
def registration(request, category_id, event_id):
"""Add a new registration to a category."""
cat = Category.objects.get(id=category_id)
myevent = Event.objects.get(id=event_id)
if request.method != 'POST':
form = RegistrationForm()
else:
form = RegistrationForm(request.POST)
try:
validate_category_full(cat.id)
except(ValidationError):
return render(request, 'events/event.html',
{'myevent': myevent,
'error_message': 'Event is full.',})
else:
if form.is_valid():
reg = form.save()
return HttpResponseRedirect(reverse('events:category',
args=(cat.id,)))
context = {'form': form, 'cat': cat, 'myevent': myevent}
return render(request, 'events/registration.html', context)`
Mostly a wild guess since I don't know what you mean by "the event-site is not fully loaded" (and assuming "event-site" and "formular-site" really mean "event page" and "formular page"):
First: you shouldn't render the template from another view but redirect to that view instead. If you want to display a message to the user (the "event is full" message in this case) when he gets redirected back to the other view, use the messages framework:
from django.contrib import messages
def registration(request, category_id, event_id):
# snip a lot of code
try:
validate_category_full(cat.id)
except(ValidationError):
messages.error(request, 'Event is full.')
return redirect(your_event_view_name, whatever_args_it_takes)
A few notes why we're at it:
=> Use queryset.count() instead of len(queryset.all())
def validate_category_full(category_id):
cat = Category.objects.get(id=category_id)
regs = cat.registration_set.count()
if regs >= cat.max_people:
raise ValidationError('Category is already full.')
Also since you already have the category, you may want to pass it instead of the category.id
I have the below view;
class MemberAdd(View):
def get(self, request):
raise Http404
def post(self, request, pk):
form = AddMemberForm(request.POST)
if form.is_valid():
Station.objects.get(pk=pk).members.add(
form.cleaned_data['user']
)
return HttpResponseRedirect(reverse("home_station",
kwargs={'pk':pk},
)
)
The AddMemberForm;
class AddMemberForm(Form):
user = forms.ModelChoiceField(queryset=User.objects.all(),
empty_label='Choose a User',
)
As you can see after addition to a ManyToMany relation the view redirects to a page I have as homepage for a particular model object. I have in it the objects in the ManyToMany relation listed and I indeed find the newly added object there.
But when I run the below test;
class TestStationManagement(TestCase):
.
.
.
def test_add_member(self):
"""
Tests addition of a member to station
"""
user1 = User.objects.create_user(username='someusername',
password='somepassword')
user1.save()
station = Station.objects.create(name='somename',
address='someaddress',
owner=user1)
station.save()
user2 = User.objects.create_user(username='someotherusername',
password='someotherpassword')
user2.save()
self.client.post(reverse('add_member',
kwargs={'pk':station.pk}),
{'user':user2},
follow=True)
self.assertIn(user2, station.members.all())
I get an assertion error, E AssertionError: <User: someotherusername> not found in [].
See the mistake? Please help out.
You have to send the user's id.
This might work :
add_member_post = self.client.post(reverse('add_member',
kwargs={'pk':station.pk}),
{'user':user2.id},
follow=True)
And check if the request actually worked :
self.assertEqual(add_member_post.status_code, 200)
station remains the instance you created inside the test, and is not affected by anything that happens in your view. You'd need to reload it from the database:
self.client.post(...)
station = Station.objects.get(pk=station.pk)
self.assertIn(...)
I got, following Error messages,
TypeError at /save/ int() argument must be a string or a number, not
'SimpleLazyObject'
While executing following form.
views.py
def bookmark_save_page(request):
if request.method == 'POST':
form = BookmarkSaveForm(request.POST)
if form.is_valid():
# create or get link
link, dummy = Link.objects.get_or_create(
url = form.cleaned_data['url']
)
# create or get bookmark
bookmark, created = Bookmark.objects.get_or_create(
user=request.user,
link=link
)
# update bookmarks title
bookmarks.title = form.cleaned_data['title']
# if the bookmark is being updated, clear old tag list.
if not created:
bookmark.tag_set.clear()
# create new tag list
tag_names = form.cleaned_data['tags'].split()
for tag_name in tag_names:
tag, dummy = Tag.objects.get_or_create(name=tag_name)
bookmark.tag_set.add(tag)
# save bookmark to database.
bookmark.save()
return HttpResponseRedirect(
'/user/%s/' % request.user.username
)
else:
form = BookmarkSaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('bookmark_save.html', variables)
I thought I got error because I passed link at
bookmark, created = Bookmark.objects.get_or_create(
user=request.user,
link=link
)
But I can not figure out how to fix it.
How can I fix it?
The likely cause is that you're setting user = request.user, where request.user is not a real User object, but a SimpleLazyObject instance. See django: Purpose of django.utils.functional.SimpleLazyObject? for more details, but using request.user.id should fix your issue.
You have to login when running this piece of code on localhost. Otherwise the request.user will be a SimpleLazyObject, then the errors comes out.
Here you trying to create a Bookmark object based on request.user , but request.user is a SimpleLazyObject , so we can get a more secure user object by :
from django.contrib import auth
current_user = auth.get_user(request)
and further your query should be
bookmark, created = Bookmark.objects.get_or_create(
user=current_user,
link=link
)
Most likely the user who is loading the page is not authenticated. Therefor the error is thrown. If you want to save a request.user reference to the database, you obviously have to ensure that only authenticated users are able to call the function.
In your case there are two possibilities - add the "#login_required" decorator to the function or check if the user is authenticated inside the code. Here are the snippets:
With Decorator:
from django.contrib.auth.decorators import login_required
#login_required
def bookmark_save_page(request):
if request.method == 'POST':
form = BookmarkSaveForm(request.POST)
if form.is_valid():
# Do something
OR - checking inside the code if the user is authenticated:
def bookmark_save_page(request):
if request.method == 'POST' and request.user.is_authenticated():
form = BookmarkSaveForm(request.POST)
if form.is_valid():
# Do something
Thats strange, I had the same problem and the same solution. After trying a bunch of alternatives I went back to user = request.user and it worked
if you want to set an addition request attr, based on user -> you need to use lazy Django function which is correctly handled by Field.get_prep_value
from django.utils.functional import lazy
request.impersonator = lazy(lambda: request.user.id, int)()