How to access request url pk from throttle class? - django

I am a newbie to Django, and I am building a Django application that uses REST API. I have a throttle class, and I want to restrict users to send more than 5 number of invitations to the same user in one minute. I send a user ID with the URL.
I should access pk to use it for the caching operation. How can I access the pk from the throttle class?
For example: api/users/3299143165471965406/resend-invitation-email/
Pk will be: 3299143165471965406
views.py
#decorators.action(methods=['post'], detail=True, serializer_class=None,
permission_classes=[core_permissions.IsCompanyAdmin],
url_path='resend-invitation-email', throttle_classes=[throttles.ResendInvitationThrottle])
def resend_invitation_email(self, request, pk=None):
user = get_object_or_404(User, pk=pk)
if user.invitation_status == User.INVITATION_STATUS_ACCEPTED or user.invitation_status is None:
raise ValidationError("This user is already registered.")
else:
invitations_tasks.send_invitation_email.delay(pk)
return response.Response(status=200)
throttle:
class ResendInvitationThrottle(SimpleRateThrottle):
scope = 'invitation'
def get_cache_key(self, request, view):
invited_user_id = 1 # Here I should use PK
return self.cache_format % {
'scope': self.scope,
'ident': invited_user_id
}
Edit:
I solved it by using: invited_user_id = view.kwargs['pk']

Related

Testing custom action on a viewset in Django Rest Framework

I have defined the following custome action for my ViewSet Agenda:
class AgendaViewSet(viewsets.ModelViewSet):
"""
A simple viewset to retrieve all the Agendas
"""
queryset = Agenda.objects.all()
serializer_class = AgendaSerializer
#action(detail=False, methods=['GET'])
def get_user_agenda(self, request, pk=None):
print('here1')
id = request.GET.get("id_user")
if not id:
return Response("No id in the request.", status=400)
id = int(id)
user = User.objects.filter(pk=id)
if not user:
return Response("No existant user with the given id.", status=400)
response = self.queryset.filter(UserRef__in=user)
if not response:
return Response("No existant Agenda.", status=400)
serializer = AgendaSerializer(response, many=True)
return Response(serializer.data)
Here, I'd like to unit-test my custom action named "get_user_agenda".
However, when I'm testing, the debug output("here1") doesn't show up, and it always returns 200 as a status_code.
Here's my test:
def test_GetUserAgenda(self):
request_url = f'Agenda/get_user_agenda/'
view = AgendaViewSet.as_view(actions={'get': 'retrieve'})
request = self.factory.get(request_url, {'id_user': 15})
response = view(request)
self.assertEqual(response.status_code, 400)
Note that:
self.factory = APIRequestFactory()
Am I missing something?
Sincerely,
You will have to use the method name of the custom action and not retrieve so:
view = AgendaViewSet.as_view(actions={'get': 'get_user_agenda'})
You have to specify request url
#action(detail=False, methods=['GET'], url_path='get_user_agenda')
def get_user_agenda(self, request, pk=None):
And in my opinion it would be better to use detail=True, and get pk from url.
For example: 'Agenda/pk_here/get_user_agenda/'

Django how to write custom authenication that works for anonymos user?

I have a problem, becouse I have my django app, and whole logic related to permissions is combined in my custom permissions class - something like this:
class MyPermissions(permissions.BasePermission):
def has_permission(self, request, view):
endpoint = get_object_or_404(DataSetApiEndpoint, pk=view.kwargs["pk"])
user_is_author = False
can_read = False
can_write = False
if request.user.is_authenticated:
is_author = endpoint.author == request.user
can_read = ...
can_write = ...
user_can_read = (
is_author
or can_read
or endpoint.is_public
)
user_can_write = is_author or can_write
if request.method in permissions.SAFE_METHODS:
if user_can_read:
return True
elif user_can_write:
return True
return False
As you can see, if endpoint is public, even anonymous user can read from it. It works on localhost, but when I deployed it to server and tried to access public endpoint without logging in, I got:
"detail": "Invalid username/password."
If I was logged in, everything worked fine. As I found out, this message comes from rest BasicAuthentication. My question is, if I have permission class that takes care of whole permissions staff, is simple authentication without any checking enought? And do you have some example of authentication class tahtg would work for unathorized user when endpoint is public (all I've found worked only for logged in user)?
edit:
View looks like this:
class DataView(generics.ListCreateAPIView):
serializer_class = DataSerializer
accessed_obj_pk = "pk"
throttle_classes = [EndpointRateThrottle]
permission_classes = [MyPermissions]
def get_queryset(self):
# django query with some filters
...
def delete(self, request, *args, **kwargs):
queryset = self.get_queryset()
for data_row in queryset:
data_row.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

Login user with a login link

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.

Django REST endpoint for list of objects

I have a Django application which under /api/v1/crm/ticket can create tickets via a POST call. Now I want to be able to send different types of tickets (more then the one in the example code) to the same endpoint having a "dynamic" serializer depending on the data send. The endpoint should select the right "model" depending on the data properties existing in the request data.
I tried Django db.models but did not get them to work as I write the tickets to another external system and just pass them through, so no database table is existing and the model lacks the necessary primary key.
Can you help me out how to add more ticket types having the same endpoint?
Code
class TicketAPIView(CreateAPIView):
serializer_class = TicketSerializer
permission_classes = (IsAuthenticated,)
class TicketSerializer(serializers.Serializer):
title = serializers.CharField(max_length=256)
description = serializers.CharField(max_length=2048)
type = serializers.ChoiceField(TICKET_TYPES)
def create(self, validated_data):
if validated_data['type'] == 'normal':
ticket = TicketPOJO(
validated_data['title'],
validated_data['description'],
)
...
else:
raise Exception('Ticket type not supported')
return ticket
Files
/my-cool-app
/apps
/crm
/api
/v1
/serializers
serializers.py
__init.py
urls.py
views.py
/clients
/ticket
provider.py
/user
provider.py
/search
/config
Since your models are different for each of your ticket type, I would suggest you create an individual serializer that validates them for each different model with one generic view.
You can override the get_serializer method in your view to select an appropriate serializer depending upon the type of ticket. Something like this
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
type = self.request.data.get("type", '')
if type === 'normal':
return NormalTicketSerializer(*args, **kwargs)
elif type == 'abnormal':
return AbnormalTicketSerializer(*args, **kwargs)
else:
raise ParseError(detail='Ticket type not supported') # This will return bad request response with status code 400.
Hope this helps.

Django - How to deny the user to vote in his own objects?

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.