Getting Object based on its ID - django

Setup
I am trying to write a DeleteView that will delete an object based on its Id. The object is a journal and I want to reference the Journal that the user is currently located in. So for example if User1 is in Journal "Work" I want to delete that specific one based on journal Id and not anything else.
My understanding is that Django creates an ID fields (Autofield) for each model.
Error
This is my current view:
class DeleteJournal(LoginRequiredMixin, DeleteView):
model = Journal
tempalte_name = 'delete_journal.html'
success_url = reverse_lazy('home')
def get_object(self, queryset=None):
id = self.kwargs['id']
return self.get_queryset().filter(id=id).get()
The error I receive is this:
What is the solution to this and why is it not working?
EDIT 1
urlpatterns = [
path('', CreateToJournal.as_view(), name='to-journals'),
path('<slug:slug>', ToJournalEntriesList.as_view(), name='to-journal-entries'),
path('<slug:slug>/delete', DeleteJournal.as_view(), name='delete-journal'),
]
Please note, I do not want to delete based on slug, because a journal can have the same name and hence the same slug for different user. The only unique value is id, which is why I need to get id for the "current" journal.
Thanks a ton in advance, really appreciate anyone looking at this.

In order to delete the object without the pk or id on slug you have to use the get_object method on your DeleteView
def get_object(self):
return Journal.objects.get(pk=self.request.GET.get('pk'))
There's a similar question here:
Django UpdateView without pk in url

Alright, unfortunately the answer from Luis Silva did not help. I found out that the get_object() function requires a pk_url_kwarg set and after a little digging I found this StackOverflow post: Arguments of DetailView methods and usage of pk_url_kwarg.
This is the code for DeleteView:
views.py
class DeleteJournal(LoginRequiredMixin, DeleteView):
model = to_journal
tempalte_name = 'to_journals/delete_journal.html'
success_url = reverse_lazy('home')
pk_url_kwarg = 'id'

Related

How to display a query on DetailView in Django?

I want my "CustomDetailView" to display a query(a single "flashcard"). I was able to it by using ListView
CustomListView(ListView):
model = Flashcard
template_name = 'flashcards.html
queryset = Flashcard.objects.all()[:1]
But for DetaiView I'm getting this error
Generic detail view CustomDetailView must be called with either an object pk or a slug in the URLconf.
class CustomDetailView(DetailView):
model = Flashcard
template_name = "flashcards.html"
queryset = Flashcard.objects.all().first()
urls.py
path('', CustomDetailView.as_view(), name='flashcards'),
How to fix this?
remove first from queryset:
class CustomDetailView(DetailView):
model = Flashcard
template_name = "flashcards.html"
queryset = Flashcard.objects.all()
lookup_field = 'pk'
lookup_url_kwarg = 'pk'
enter code here
and add id to url:
path('/<int:pk>/', CustomDetailView.as_view(), name='flashcards'),
You need your pk or slug in
path('', CustomDetailView.as_view(), name='flashcards'),
See an example from the docs:
urlpatterns = [
path('<slug:slug>/', ArticleDetailView.as_view(), name='article-detail'),
]
This is because DetailView is inheriting from SingleObjectMixin, and it fetches objects that way. If you want, jump to definition of this class in your IDE to see its implementation of get_queryset and get_object.
How you call your pk or slug really depends on your model, the Flashcard. If your primary_key field is an Integer, I think you'll be fine writing
path('<int:pk>', CustomDetailView.as_view(), name='flashcards'),
edit: as Pourya Mansouri wrote, you do need to remove queryset attribute in your case too
The slug you can customize with slug_field and similar attributes.
You can define a completely custom object fetching behavior by overriding get_object or get_queryset. Here's a random examples of doing that I found on the internet:
Overriding get_object: https://www.valentinog.com/blog/detail/
Overriding get_queryset: https://www.agiliq.com/blog/2019/01/django-when-and-how-use-detailview/
Maybe should have better communicated. Sorry for my English.
I wanted to return a random query when I clicked on a link a show it on detailView. I was able to it by this. Don't think it's efficient. If anyone has any idea share it.
def get_object(self):
queryset = Flashcard.objects.order_by('?').first()
return queryset

Form Create View and 3 hierarchical models - Limit Choices for ForeignKey

I am trying to create an 'Expense Tracker'. I have query regarding create view and Models with 3 hierarchical levels. For eg:-
class ExpenseYear(models.Model):
year = models.PositiveIntegerField()
class ExpenseMonth(models.Model):
month = models.CharField(max_length = 14,blank = False)
month_year = models.ForeignKey(ExpenseYear,related_name = 'month',on_delete=models.CASCADE)
class Expenses(models.Model):
expense = models.PositiveIntegerField()
expense_month = models.ForeignKey(ExpenseMonth,related_name = 'expense', on_delete=models.CASCADE)
Now, while creating CreateView for 'Expenses' model, i am facing an issue. I want to display only the months in that particular year (in 'expense_month' foreignkey), but all months of all the years are being displayed in the 'expense_month' foreignkey.
This is what i am getting - CreateView
I searched stackoverflow about limiting foreignkey choices and ended up finally with another code, but now no months are displayed in the foreign key
Forms.py
#forms.py
class ExpensesForm(forms.ModelForm):
class Meta():
model = Expenses
fields = ('expenses','expense_month')
def __init__(self, *args, **kwargs):
month_year_id = kwargs.pop('month_year_id')
super(ExpensesForm, self).__init__(*args, **kwargs)
self.fields['expense_month'].queryset = ExpenseMonth.objects.filter(month_year_id=month_year_id)
Views.py
#views.py
class ExpensesCreateView(CreateView):
model = models.Expenses
form_class = ExpensesForm
def get_form_kwargs(self):
kwargs = super(ExpensesCreateView, self).get_form_kwargs()
kwargs.update({'month_year_id': self.kwargs.get('month_year_id')})
return kwargs
However, as i said no months are displayed if i write this code.
This is what i am getting if i write the above code - CreateView
How to limit foreignkey choices? i checked Django Documentation, but i could find any particular answer for this. Can please Help me out. I have searched many questions related to this in stackoverflow and other websites. Can you please answer this question in this context itself (pls don't reply with a genenral code as, i am new to django and find it difficult to understand general answers)
Thanks in Advance !
Urls.py
#urls.py
urlpatterns = [
path('year/<int:pk>/<int:pk2>/create_expense/',views.ExpensesCreateView.as_view(),name ='create_expense'),
]
The issue is with the get_form_kwargs in your view:
def get_form_kwargs(self):
kwargs = super(ExpensesCreateView, self).get_form_kwargs()
kwargs.update({'month_year_id': self.kwargs.get('month_year_id')})
return kwargs
If you print(self.kwargs.get('month_year_id')) it will probably be None.
self.kwargs are the URL keyword arguments you provide to the view, in your case your url is providing two arguments pk and pk2 here:
path('year/<int:pk>/<int:pk2>/create_expense/',views.ExpensesCreateView.as_view(),name ='create_expense'),
So you have two choices, either rename the argument in the path so it is <int:month_year_id> (I'm not sure whether you should rename pk or pk2 just from your code snippets).
or
In get_form_kwargs get either pk or pk2 from self.kwargs instead of month_year_id, for example:
kwargs.update({'month_year_id': self.kwargs.get('pk2')})

How to implement a simple "like" feature in Django REST Framework?

I'm a beginner building the backend API for a social media clone using DRF. The frontend will be built later and not in Django. I'm currently using Postman to interact with the API.
I'm trying to implement a "like" feature as you would have on Facebook or Instagram. I cannot send the correct data with Postman to update the fields which bear the many-to-many relationship.
Here is some of my code:
models.py
class User(AbstractUser):
liked_haikus = models.ManyToManyField('Haiku', through='Likes')
pass
class Haiku(models.Model):
user = models.ForeignKey(User, related_name='haikus', on_delete=models.CASCADE)
body = models.CharField(max_length=255)
liked_by = models.ManyToManyField('User', through='Likes')
created_at = models.DateTimeField(auto_now_add=True)
class Likes(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
haiku = models.ForeignKey(Haiku, on_delete=models.CASCADE)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'password', 'url', 'liked_haikus']
extra_kwargs = { 'password' : {'write_only': True}}
def create(self, validated_data):
password = validated_data.pop('password')
user = User(**validated_data)
user.set_password(password)
user.save()
token = Token.objects.create(user=user)
return user
class HaikuSerializer(serializers.ModelSerializer):
class Meta:
model = Haiku
fields = ['user', 'body', 'liked_by', 'created_at']
class LikesSerializer(serializers.ModelSerializer):
model = Likes
fields = ['haiku_id', 'user_id']
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
#action(detail=True, methods=['get'])
def haikus(self, request, pk=None):
user = self.get_object()
serializer = serializers.HaikuSerializer(user.haikus.all(), many=True)
return Response(serializer.data)
class UserCreateViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.AllowAny]
class HaikuViewSet(viewsets.ModelViewSet):
queryset = Haiku.objects.all()
serializer_class = HaikuSerializer
permission_classes = [permissions.IsAuthenticated]
class LikesViewSet(viewsets.ModelViewSet):
queryset = Likes.objects.all()
serializer_class = LikesSerializer
permission_classes = [permissions.IsAuthenticated]
urls.py
router = routers.DefaultRouter(trailing_slash=False)
router.register('users', views.UserViewSet)
router.register('haikus', views.HaikuViewSet)
router.register('register', views.UserCreateViewSet)
router.register('likes', views.LikesViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api-auth-token', obtain_auth_token, name='api_token_auth')
]
Using the Django Admin I can manually set users to like posts and the fields in the db will update and reflect in API requests.
With Postman, I've tried sending both PUT and PATCH to, for example:
http://127.0.0.1:8000/haikus/2
with "form data" where key ="liked_by" and value="3" (Where 3 is a user_id). I got a 200 response and JSON data for the endpoint back, but there was no change in the data.
I've tried GET and POST to http://127.0.0.1:8000/likes and I receive the following error message:
AttributeError: 'list' object has no attribute 'values'
I've looked at nested-serializers in the DRF docs, but they don't seem to be quite the same use-case.
How can I correct my code and use Postman to properly update the many-to-many fields?
I think I need to probably write an update function to one or several of the ViewSets or Serializers, but I don't know which one and don't quite know how to go about it.
All guidance, corrections and resources appreciated.
To update the liked_by Many2Many field, the serializer expect you to provide primary key(s).
Just edit your HaikuSerializer like the following. It will work.
class HaikuSerializer(serializers.ModelSerializer):
liked_by = serializers.PrimaryKeyRelatedField(
many=True,
queryset=User.objects.all())
class Meta:
model = models.Haiku
fields = ['created_by', 'body', 'liked_by', 'created_at']
def update(self, instance, validated_data):
liked_by = validated_data.pop('liked_by')
for i in liked_by:
instance.liked_by.add(i)
instance.save()
return instance
adnan kaya has provided the correct code and I have upvoted him and checked him off as the correct answer. I want go through his solution to explain it for future readers of this question.
liked_by = serializers.PrimaryKeyRelatedField(
many=True,
queryset=User.objects.all())
You can read about PrimaryKeyRelatedField here: https://www.django-rest-framework.org/api-guide/relations/
Since liked_by is a ManyToManyField it has special properties in that ManyToMany relations create a new table in the DB that relates pks to each other. This line tells Django that this field is going to refer to one of these tables via its primary key. It tells it that liked by is going to have multiple objects in it and it tells it that these objects are going to come from a particular queryset.
def update(self, instance, validated_data):
liked_by = validated_data.pop('liked_by')
for i in liked_by:
instance.liked_by.add(i)
instance.save()
return instance
ModelSerializers is a class that provides its own built in create and update functions that are fairly basic and operate in a straightforward manner. Update, for example, will just update the field. It will take the incoming data and use it to replace the existing data in the field it is directed at.
You can read more about ModelSerializers here: https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
You can overwrite these functions and specify custom functions by declaring them. I have declared update here. Update is a function that takes 3 arguments. The first is self. You can call this whatever you want, but there is a strong convention to call it self for readability. Essentially this is importing the class the function belongs, into the function so you can utilize all that classes functions and variables. Next is instance. Instance is the data that is currently in the entry you are trying to update. It is a dictionary like object. Finally, there is validated_data. This is the data you are trying to send to the entry to update it. When using form data, for example, to update a database, this will be a dictionary.
liked_by = validated_data.pop('liked_by')
Because validated_data is a dictionary you can use the .pop() method on it. Pop can take the key of the dictionary and "pop it off" leaving you with the value (more formally, .pop('key') will return its 'value'). This is nice because, at least in my case, it is the value that you want added to the entry.
for i in liked_by:
instance.liked_by.add(i)
this is a simple python for-loop. A for loop is here because in my use-case the value of the validated_data dictionary is potentially a list.
The .add() method is a special method that can be used with ManytoMany relationships. You can read about the special methods for ManytoMany relations here: https://docs.djangoproject.com/en/3.1/ref/models/relations/
It does what it advertises. It will add the value you send send to it to data you call it for, instead of replacing that data. In this case it is instance.liked_by (the current contents of the entry).
instance.save()
This saves the new state of the instance.
return instance
returns the new instance, now with the validated data appended to it.
I'm not sure if this is the most ideal, pythonic, or efficient way implementing a like feature to a social media web app, but it is a straightforward way of doing it. This code can be repurposed to add all sorts of many-to-many relationships into your models (friends lists/followers and tags for example).
This is my understanding of what is going on here and I hope it can help make sense of the confusing topic of ManytoMany relationships for clearer.

CreateOrUpdateView for Django Models

In my models I have a ForeignKey relationship like this:
class Question(models.Model):
question = models.TextField(null=False)
class Answer(models.Model):
question = models.ForeignKey(Question, related_name='answer')
user = models.ForeignKey(User)
class Meta:
unique_together = (("question", "user"),)
the corresponding URL to submit an answer contains the id of the question, like this:
url(r'^a/(?P<pk>\d+)/$', AnswerQuestion.as_view(), name='answer-question'),
With user coming from self.request.user, I am trying to get something like a CreateOrUpdateView, to allow some convenient navigation for the user and URL scheme.
Until now I tried this with:
class AnswerQuestion(LoginRequiredMixin, CreateView):
and add initial value, but that isn't clean because of pk. With an UpdateView I run into problems because I have to set default values for the form.
Has anybody done something like this? I'd rather avoid having a Create and Update view for the same Answer.
The UpdateView and CreateView are really not that different, the only difference is that UpdateView sets self.object to self.get_object() and CreateView sets it to None.
The easiest way would be to subclass UpdateView and override get_object():
AnswerQuestionView(LoginRequiredMixin, UpdateView):
def get_object(queryset=None):
if queryset is None:
queryset = self.get_queryset()
# easy way to get the right question from the url parameters:
question = super(AnswerQuestionView, self).get_object(Question.objects.all())
try:
obj = queryset.get(user=self.request.user, question=question)
except ObjectDoesNotExist:
obj = None
return obj
Returns the right answer if it exists, None if it does not. Of course add any attributes you need to the class, like model, form_class etc.

Django: hiding the primary key (pk) and using DetailView with get_queryset

Following up on this thread: django: How do I hash a URL from the database object's primary key?, I would like to hide primary keys from users and use Detailview in my urlconf. I was able to accomplish this hiding using bitwise XOR for the most part (it worked in my view functions), until I got to the part where I had to "unmask" the masked primary key that was sent in the url to my subclass of DetailView.
How do I "unmask" my pk_masked named group before sending it to my DetailViewFilteredOnUser(DetailView) instance? Is there a way to send mask_toggle(pk_unmasked) to my call to DetailViewFilteredOnUser right in the urlconf? In searching for solutions, I found something about pk_url_kward in the Django documentation, but I couldn't get it to work and anyways I don't think that can help me perform operations on the primary key that DetailView operates on.
Here is my masking function:
def mask_toggle(number_to_mask_or_unmask):
return int(number_to_mask_or_unmask) ^ settings.MASKING_KEY
My models are "pkgs" that contain "items":
class Pkg(models.Model):
user = models.ForeignKey(User, editable=False)
tracking_number = models.CharField(max_length=60, unique=True)
class Item(models.Model):
pkg = models.ForeignKey(Pkg)
description = models.CharField(max_length=300)
Here is what is in my urls.py:
class ListViewFilteredOnUser(ListView):
def get_queryset(self):
return Pkg.objects.order_by('-created_at').filter(user=self.request.user)
class DetailViewFilteredOnUser(DetailView):
def get_queryset(self):
qs = super(DetailViewFilteredOnUser, self).get_queryset()
return qs.filter(user=self.request.user)
....
url(r'^(?P<pk_masked>\d+)/$',
login_required(DetailViewFilteredOnUser.as_view( model=Pkg,
template_name='pkgs/detail.html'
)),
name='detail'),
So the problem is that if my named group in my urlconf is "pk", then a masked primary key (because the masked key is what is in the url) is sent to DetailView. If my named group in my urlconf is "pk_masked", then I need to do pk=mask_toggle(pk_masked) somewhere, and I can't figure out where or how to do this. Thanks.
If I understood your problem correctly, you need to override get_object (which makes get_queryset pretty much irrelevant, but you can still use it for clarity). Something like:
class DetailViewFilteredOnUser(DetailView):
model = Pkg
template_name = 'pkgs/detail.html'
def get_queryset(self):
return super(DetailViewFilteredOnUser, self).get_queryset().filter(user=self.request.user)
def get_object(self):
return self.get_queryset().get(pk=mask_toggle(self.kwargs.get("pk_masked"))
(Of course, don't forget to catch exceptions, I left that out for clarity and brevity.)