Referencing User ID in Django URL dispatcher - python

I'm trying to implement a search feature where user's can search an email on a React frontend and it'll return that email's top 5 love languages. Currently the url path requires the primary key of a love language model, but I want it to use the user id. I have this Django URL set as:
path('love-languages/', LovesView.as_view(), name='love-languages'),
path('love-languages/<int:pk>', LoveView.as_view(), name='love-languages')
Relevant love language model:
class Love(models.Model):
# Obtaining the user from the user model
user = models.ForeignKey(
get_user_model(),
on_delete = models.CASCADE
)
# Defining the dropdown choices
class LoveLanguages(models.TextChoices):
ACTS_OF_SERVICE = 'Acts of Service'
RECEIVING_GIFTS = 'Receiving Gifts'
QUALITY_TIME = 'Quality Time'
WORDS_OF_AFFIRMATION = 'Words of Affirmation'
PHYSICAL_TOUCH = 'Physical Touch'
one = models.CharField(max_length=20, choices=LoveLanguages.choices)
two = models.CharField(max_length=20, choices=LoveLanguages.choices)
three = models.CharField(max_length=20, choices=LoveLanguages.choices)
four = models.CharField(max_length=20, choices=LoveLanguages.choices)
five = models.CharField(max_length=20, choices=LoveLanguages.choices)
and love language views:
class LovesView(APIView):
def get(self, request):
loves = Love.objects.filter(user=request.user.id)
data = LoveSerializer(loves, many=True).data
return Response(data)
def post(self, request):
request.data['user'] = request.user.id
love = LoveSerializer(data=request.data)
if love.is_valid():
love.save()
return Response(love.data, status=status.HTTP_201_CREATED)
else:
return Response(love.errors, status=status.HTTP_400_BAD_REQUEST)
class LoveView(APIView):
def get(self, request, pk):
love = get_object_or_404(Love, pk=pk)
# if request.user != love.user:
# raise PermissionDenied('Unauthorized, you are not signed in as this user')
# else:
data = LoveSerializer(love).data
return Response(data)
def delete(self, request, pk):
love = get_object_or_404(Love, pk=pk)
if request.user != love.user:
raise PermissionDenied('Unauthorized, you are not signed in as this user')
else:
love.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def put(self, request, pk):
love = get_object_or_404(Love, pk=pk)
if request.user != love.owner:
raise PermissionDenied('Unauthorized, you are not signed in as this user')
else:
updated_love = LoveSerializer(love, data=request.data)
if updated_love.is_valid():
updated_love.save()
return Response(updated_love.data)
else:
return Response(love.errors, status=status.HTTP_400_BAD_REQUEST)
So, if this were the data returned:
{
"id": 4,
"user": 2,
"one": "Acts of Service",
"two": "Receiving Gifts",
"three": "Quality Time",
"four": "Words of Affirmation",
"five": "Physical Touch"
}
The URL would have to be .../love-languages/4. I want the URL to be .../love-languages/2 because I am not sure how I can access the pk of 4 when only an email is entered. I've thought about referencing the love language model in the user model, but I think there should be a way to replace <int:pk> in the URL with something like <int:user>? I tried that and it did not work, I got an error saying TypeError: get() got an unexpected keyword argument 'user' I was reading the django docs for URL dispatcher but couldn't find anything useful at the moment.
I also tried changing the get request in the views to
love = get_object_or_404(Love, pk=request.user.id)
but that didn't work. It only returns the same data regardless of what id is entered in the url.

If I understand the question correctly here is what you need to do.
Django allows you to query a model and its relationship with __.
So to make a query about Love model with respect to user you would do something like this.
try:
love = Love.objects.get(user__email=variable_email)
except Love.DoesNotExist:
raise Http404
there are 2 ways to detect email in url.
you can simply use <str:variable_email> and then clean the email in you view before using it to query your database.
or you can use regex to detect in url pattern.( like question asked here)

Related

Direct assignment to the forward side of a many-to-many set is prohibited. Use Class.set() instead

When I try to save Class which is in many to many relationship django throws the following error
TypeError at /class-create/
Direct assignment to the forward side of a many-to-many set is prohibited. Use Class.set() instead.
My views.py looks like this
#login_required()
def create_class(request):
tea_user = request.user.username
validate = teacher_validation(tea_user)
if validate:
if request.method == 'POST':
Link = request.POST.get('link')
Subject = request.POST.get('Subject')
Class = request.POST.get('Class')
teacher_user = Teacher.objects.get(User=request.user)
teacher = Teacher.objects.get(id=teacher_user.id)
created_class = Online_Class(Link=Link, Subject=Subject, Created_by =teacher, Class=Class)
created_class.save()
return render(request, 'online_class/Teacher/class-create.html')
else:
messages.warning(request, 'Sorry You Dont have Permission to access this page')
return redirect('logout')
And my models.py file looks like this
class Online_Class(models.Model):
Created_by = models.ForeignKey(Teacher, on_delete=models.DO_NOTHING)
Class = models.ManyToManyField(Classes)
Subject = models.CharField(max_length=100)
Link = models.CharField(max_length=200)
Joined_by = models.ManyToManyField(Student, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
choice = (('Yes','Yes'),('No', 'No'))
Class_Ended = models.CharField(choices=choice, default='No', max_length=10)
Please help me figure it out
You can not set class=Class in:
created_class = Online_Class(Link=Link, Subject=Subject, Created_by =teacher, Class=Class)
since Class is a ManyToManyField, and thus can not be set like that, you first create the OnelineClass, and then add an entry (or more entries) with created_class.Class.set(&hellip):
#login_required
def create_class(request):
tea_user = request.user.username
validate = teacher_validation(tea_user)
if validate:
if request.method == 'POST':
Link = request.POST.get('link')
Subject = request.POST.get('Subject')
Class = request.POST.get('Class')
teacher_user = Teacher.objects.get(User=request.user)
teacher = Teacher.objects.get(id=teacher_user.id)
created_class = Online_Class.objects.create(
Link=Link,
Subject=Subject,
Created_by =teacher
)
created_class.Class.set([Class])
return render(request, 'online_class/Teacher/class-create.html')
else:
messages.warning(request, 'Sorry You Dont have Permission to access this page')
return redirect('logout')
Note: normally a Django models, just like all classes in Python are given a name in PascalCase, not snake_case, so it should be: OnlineClass instead of Online_Class.
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: class instead of Class.
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.

Grab an object in Django

This is probably very simple and basic but I'm struggling with grabbing a newly-created object in Django. It is for a basic library-style app. Over in models, I do this to create a Book object:
def add_book(self, postData, user_id):
title = postData['title']
first_name = postData['first_name']
last_name = postData['last_name']
user_obj = User.objects.get(id=user_id)
if not Author.objects.filter(first_name=first_name, last_name=last_name).exists():
author_obj = Author.objects.create(first_name=first_name, last_name=last_name)
else:
author_obj = Author.objects.get(first_name=first_name, last_name=last_name)
return self.create(title=postData['title'], created_by=user_obj, author=author_obj)
Then in views, I call that method and wish to redirect to a page specifically for that newly-created object. I think you can see that I have most of the code down, but don't know what to put in place of the "????".
def books_add(request):
if request.method == "POST":
errors = Book.objects.book_validation(request.POST)
if not errors:
Book.objects.add_book(request.POST, request.session['uid'])
book_id = Book.objects.get(????).id
return redirect('/books/book/{}/'.format(book_id))
else:
context = {
'errors' : errors,
}
1st part use get_or_create for retrieve or create a model entry
def add_book(self, postData, user_id):
title = postData['title']
first_name = postData['first_name']
last_name = postData['last_name']
user_obj = User.objects.get(id=user_id)
author_obj, created = Author.objects.get_or_create(first_name=first_name, last_name=last_name)
return self.create(title=postData['title'], created_by=user_obj, author=author_obj)
2nd part, return self.create return a Book entity :
def books_add(request):
if request.method == "POST":
errors = Book.objects.book_validation(request.POST)
if not errors:
book = Book.objects.add_book(request.POST, request.session['uid'])
return redirect('/books/book/{}/'.format(book.id))
else:
context = {
'errors' : errors,
}
There are some issues here. At the very least, look at Django Forms before you go much further. This is what a view that creates an object could look like:
def add_book(request):
if request.POST:
author, created = Author.objects.get_or_create(first_name=first_name,
last_name=last_name)
book = Book(title = request.POST['title'],
user_obj = request.GET['user'],
author = author,)
book.save()
return redirect('/books/book/{}/'.format(book.id))
else:
return render(request, 'book_form.html')
You really need to look into ModelForms to handle your POSTs. But start with looking at Forms.

How to provide additional data in "PUT" request for update in Django REST Framework?

Here is what I have so far:
[serializers.py]
class EmployeeSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
user = UserSerializer(required=False)
company = serializers.CharField(read_only=True)
employee_type = serializers.ChoiceField(choices=EMPLOYEE_TYPES, default='manager')
is_blocked = serializers.BooleanField(required=False)
def update(self, instance, validated_data):
instance.user = validated_data.get('user', instance.user)
instance.company = validated_data.get('company', instance.company)
instance.employee_type = validated_data.get('employee_type', instance.employee_type)
instance.is_blocked = validated_data.get('is_blocked', instance.is_blocked)
instance.save()
return instance
[views.py]
class EmployeeDetail(APIView):
def get_employee(self, pk):
try:
return Employee.objects.get(pk=pk)
except Employee.DoesNotExist:
raise Http404
def put(self, request, pk, format=None):
employee = self.get_employee(pk)
serializer = EmployeeSerializer(employee, data=request.data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
[request]
http -a admin:password PUT http://localhost:8000/api/employees/list/2/
As a result I am able to update that employee with id=2, but the only thing that I am able to change is "employee_type" cause it has default value = 'manager', and if "employee_type" of that employee with id=2 is let's say "admin" after my request it will become "manager". The problem is that I can't figure out how to add some extra data to my request so I would be able to change "employee_type" to "director" for example, is something like below can be accomplished ?
[request_as_I_want]
http -a admin:password PUT http://localhost:8000/api/employees/list/2/employee_type='director'/company='some_value'/
Is that can be done, or I misunderstand something ?
I assume you are using httpie. To send a PUT request to django-rest-framework, a URL and json data is needed. Here's one way to do that (notice the space between URL and data):
http -a admin:password PUT http://localhost:8000/api/employees/list/2 employee_type='director' company='some_value'
See more at https://github.com/jakubroztocil/httpie#json

Django session passes argument from a view to another but sets the same value for all user instances

I am passing an (is_followed) parameter from one class based view FollowToggleAPIView to another UserDetailAPIVIew. I do this using Django session (from reading other thread on this platform) in the hope of displaying the follow-status (True or False) of the user_to_toggle variable on the UserSingleProfileSerializer.
Here are my views:
class UserDetailAPIVIew(generics.RetrieveAPIView):
'''
Displays a list of a user's posts
'''
serializer_class = UserSingleProfileSerializer
queryset = User.objects.all()
def get_object(self):
self.object = get_object_or_404(User,
username__iexact=self.kwargs.get('username')
)
return self.object
def get_serializer_context(self):
'''
passing the extra is_following argument to the UserDetailAPIVIew
'''
context = super(UserDetailAPIVIew, self).get_serializer_context()
is_followed = self.request.session.get('followed')
context.update({'followed': is_followed})
return context
class FollowToggleAPIView(APIView):
'''
Uses the custom model manager for user toggle follow
'''
def get(self, request, username, format=None):
user_to_toggle = get_object_or_404(User, username__iexact=username)
me = request.user
message = 'Not allowed'
if request.user.is_authenticated():
is_followed = UserProfile.objects.toggle_follow(me, user_to_toggle)
request.session['followed'] = is_followed
return Response({'followed': is_followed})
return Response({'message': message}, status=400)
The toggle_follow method is defined in the custom model manager as follows:
class UserProfileManager(models.Manager):
def toggle_follow(self, user, to_toggle_user):
''' follow unfollow users '''
user_profile, created = UserProfile.objects.get_or_create(user=user)
if to_toggle_user in user_profile.following.all():
user_profile.following.remove(to_toggle_user)
added = False
else:
user_profile.following.add(to_toggle_user)
added = True
return added
class UserProfile(models.Model):
'''
Extends the Django User model
'''
user = models.OneToOneField(settings.AUTH_USER_MODEL,
related_name='profile')
following = models.ManyToManyField(settings.AUTH_USER_MODEL,
blank=True,
related_name='followed_by')
objects = UserProfileManager()
def get_absolute_url(self):
return reverse_lazy('profiles:detail',
kwargs={'username':self.user.username})
def __str__(self):
return 'Username: {} [ Followers ({});
Following({})]'.format(self.user.username,
self.user.followed_by.all().count(),
self.following.all().count())
The urls.py:
urlpatterns = [
url(r'^(?P<username>[\w.#+-]+)/$', UserDetailAPIVIew.as_view(),
name='user-posts-api'),
url(r'^(?P<username>[\w.#+-]+)/follow/$',
FollowToggleAPIView.as_view(), name='follow-api'),
]
The only problem is that the value of (is_followed) displayed in UserSingleProfileSerializer is set for all user instances at once (not for the specific user we want to follow).
I am certainly not following/unfollowing all users at the same time (since the FollowToggleAPIView targets a specific user by his username).
I want to know how can I transfer the value of (is_followed) only to the specific user (user_to_toggle) in the UserDetailAPIVIew. Thank you in advance.
The session is completely the wrong thing to use here. You're storing a single "followed" value which only records the last user they toggled and has no relation to the profile they're actually viewing.
Instead of doing this, you should simply query in the UserDetailAPIVIew the followed status of the specific user.
def get_serializer_context(self):
context = super(UserDetailAPIVIew, self).get_serializer_context()
is_followed = self.request.user.profile.following.filter(username=self.object).exists()
context.update({'followed': is_followed})
return context
Note also, your toggle method is itself very inefficient - there's no need to retrieve every follow from the database just to check whether the current user is among them. Use exists again:
user_profile, created = UserProfile.objects.get_or_create(user=user)
if user_profile.following.filter(username=to_toggle_user).exists():

How can I add an object to a M2M field on my users profile from django rest frameworks class based create view?

I am trying to have a logged in user add Media to their media field in the users profile in a DRF generic create view. Has anyone tried this? Here's my view and model:
class MediaCreate(generics.CreateAPIView):
"""
To create a media object, send a post request to:
/profiles/media/create/
In the format:
Audio: "audio file upload"
Title: "char field"
"""
queryset = Media.objects.all()
serializer_class = MediaSerializer
class Musician(ProfileModel):
summary = models.TextField(blank=True)
company = models.CharField(max_length=60, blank=True)
media = models.ManyToManyField('Media', blank=True)
timestamp = models.DateTimeField(auto_now_add=True, blank=True)
def __str__(self):
return '{}'.format(self.user.username)
So I decided to do this with a function api view instead so I can add the model to the profile using the request and not requiring any get request
I know the check if method == Post is redundant but it doesn't harm anything:
#api_view(['POST'])
def MediaCreate(request):
context = {}
logged_on = False
if request.user.is_authenticated():
logged_on = True
visitor = request.user.musician
serializer = MediaSerializer(data=request.data)
if request.method == "POST":
if serializer.is_valid():
serializer.save()
try:
x = serializer.instance
visitor.media.add(x)
context['upload'] = True
except:
error = "Media Not Added to Profile"
context['logged_on',
'error',
'upload'] = logged_on, error, False
return JsonResponse(
data=context,
status=status.HTTP_400_BAD_REQUEST)
context['logged_on'] = logged_on
return JsonResponse(data=context, status=status.HTTP_200_OK)
all you need to do define in your serializers.py the two classes:
class MediaSerializer(serializers.ModelSerializer):
class Meta:
model = Media
class MusicianSerializer(serializers.ModelSerializer):
media = MediaSerializer(many=True, read_only=true)
class Meta:
model = Musician
In order to update the media field for specific musician you send only the media_id and not the whole object as show the following example:
data = {
"summary": "text summary here"
"company": "Company A"
"media_id": 3
}
in your views.py you should define the following view and override get_object to fit your needs:
class MusicianUpdatingApiView(generics.UpdateAPIView):
"""
To add a media to a user, send a post request to:
/profiles/:id/media
"""
serializer_class = MusicianSerializer
def get_object(self):
summary = self.request.data.get("summary")
company = self.request.data.get("company")
media_id = self.request.data.get("media_id")
musician_id = self.kwargs['id'] # cause it send in the url
data = {
"summary": summary
"company": company
"media_id": media_id
}
updated_musician, created = Musician.objects.update_or_create(
id=musician_id,
defaults=data
)
return updated_musician
in urls.py
url(r'^profiles/(?P<id>\d+)/media$', MusicianUpdatingApiView.as_view()),

Categories