How to pass parameter to get_queryset in Django - django

I have Django API built in and I have endpoint the return all object. I want the user to provide me with keyword to filter this queryset. What is the best way to do it. and how to do it plz ?
is it in get_queryset? if yes can you help me !?

You have access to the GET parameters (in the querystring) with self.request.GET [Django-doc].
So for example if there is a parameter ?category=foo, you can access foo with self.request.GET['category'], or self.request.GET.get('category') if you want it to return None in case it is missing.
You thus can filter for example with:
from rest_framework import generics
from app.models import SomeModel
from app.serializers import SomeSerializer
class UserList(generics.ListAPIView):
model = SomeModel
def get_queryset(self):
qs = super().get_queryset()
category = self.request.GET.get('category')
if category is None:
return qs
return qs.filter(category=categry)

just give pass the parameter with some default value:
def get_queryset(self, some_thing=default):
.
.
.
It will work

Related

Django rest framework complex return info for a single api

How to return complex information in a single api under Django rest framework?
Assuming I have a model:
class Apple(models.Model):
color
size
shape
With a single api: /api/get-apples, I want to return a json like below:
{"red": {"round":[0.1,0.2,0.3],"spigold":[0.3,0.4,0.5]},
"yellow":{"round":[0.1,0.2,0.4],"spigold":[0.2,0.4,0.5]}}
What will be the best way to achieve this?
Create a serializers.py in your app's folder and add this code into it.
from rest_framework import serializers
class AppleSerializer(serializers.ModelSerializer):
class Meta:
model = Apple
fields = ('color', 'size', 'shape',)
In your views.py:
from rest_framework.generics import ListAPIView
from .serializers import AppleSerializer
class get_apples(ListAPIView):
serializer_class = AppleSerializer
def get_queryset(self):
# Here create your queryset.
queryset = Apple.objects.all()
return queryset
In your urls.py:
url(r'^api/get_apples/', views.get_apples.as_view(), name='get_apples'),
And you are good to go.
Output will be like this.
Let's say you have 2 apples.
{{"color": "red", "size": "blabla", "shape": "round"},{...(another apple json)}}
I'd edit my previous answer but I think it is a good example of using serializers with covering view,urls and serializer parts. Therefore, I didn't want to delete it :)
Here is how to return a complex json structure.
As I mentioned before, as far as I know, we can't do something like that by using rest framework's serializers class because it need comparison and grouping. I'll use rest_framework's api_view structure.
Also, I didn't understand types of size and shape in your model and what is your desired output. Therefore, this might be wrong but you'll get the idea anyway.
from rest_framework.decorators import api_view
from django.http import HttpResponse
import json
#api_view(['GET'])
def get_apples(request):
# define your queryset here as you want.
basket = Apple.objects.all()
data = {} # empty dictionary
for apple in basket:
if data.get(apple.color, None) is not None: # if same color of apple exists in basket
# check if shape exists or not.
if data.get(apple.color).get(apple.shape, None) is not None:
data.get(apple.color).get(apple.shape).append(apple.size)
else:
data.get(apple.color)[apple.shape] = [apple.size]
else:
data[apple.color] = {apple.shape: [apple.size]}
return HttpResponse(json.dumps(data), content_type='application/json; charset=utf-8')
I didn't test this code but probably this will work. Let me know if this works or not!

Django UpdateView without pk in url

Is it possible eliminate pk from url related to UpdateView?
For example, if I have
url(r'^myobj/update/(?P<pk>\d+)/$', views.UpdateMyObj.as_view(), name="update")
is there any way to write it like
url(r'^myobj/update/$', views.UpdateMyObj.as_view(), name="update")
and then send pk as a parameter in POST or GET request?
Yes it is possible you just need to override the get_object method:
from django.views.generic.edit import UpdateView
class UpdateMyObj(UpdateView):
# .....
def get_object(self):
return MyModel.objects.get(pk=self.request.GET.get('pk')) # or request.POST
Sometimes it works like this
class MyViewUpdate(UpdateView):
def get_object(self, queryset=None):
obj = self.model.objects.get(my_id_or_name_of_field=self.kwargs['pk_or_field_name']) # instead of self.request.GET or self.request.POST
return obj

Django Admin linking to related objects

My app has users who create pages. In the Page screen of the admin, I'd like to list the User who created the page, and in that list, I'd like the username to have a link that goes to the user page in admin (not the Page).
class PageAdmin(admin.ModelAdmin):
list_display = ('name', 'user', )
list_display_links = ('name','user',)
admin.site.register(Page, PageAdmin)
I was hoping that by making it a link in the list_display it would default to link to the actual user object, but it still goes to Page.
I'm sure I'm missing something simple here.
Modifying your model isn't necessary, and it's actually a bad practice (adding admin-specific view-logic into your models? Yuck!) It may not even be possible in some scenarios.
Luckily, it can all be achieved from the ModelAdmin class:
from django.urls import reverse
from django.utils.safestring import mark_safe
class PageAdmin(admin.ModelAdmin):
# Add it to the list view:
list_display = ('name', 'user_link', )
# Add it to the details view:
readonly_fields = ('user_link',)
def user_link(self, obj):
return mark_safe('{}'.format(
reverse("admin:auth_user_change", args=(obj.user.pk,)),
obj.user.email
))
user_link.short_description = 'user'
admin.site.register(Page, PageAdmin)
Edit 2016-01-17:
Updated answer to use make_safe, since allow_tags is now deprecated.
Edit 2019-06-14:
Updated answer to use django.urls, since as of Django 1.10 django.core.urls has been deprecated.
Add this to your model:
def user_link(self):
return '%s' % (reverse("admin:auth_user_change", args=(self.user.id,)) , escape(self.user))
user_link.allow_tags = True
user_link.short_description = "User"
You might also need to add the following to the top of models.py:
from django.template.defaultfilters import escape
from django.core.urls import reverse
In admin.py, in list_display, add user_link:
list_display = ('name', 'user_link', )
No need for list_display_links.
You need to use format_html for modern versions of django
#admin.register(models.Foo)
class FooAdmin(admin.ModelAdmin):
list_display = ('ts', 'bar_link',)
def bar_link(self, item):
from django.shortcuts import resolve_url
from django.contrib.admin.templatetags.admin_urls import admin_urlname
url = resolve_url(admin_urlname(models.Bar._meta, 'change'), item.bar.id)
return format_html(
'{name}'.format(url=url, name=str(item.bar))
)
I ended up with a simple helper:
from django.shortcuts import resolve_url
from django.utils.safestring import SafeText
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.utils.html import format_html
def model_admin_url(obj: Model, name: str = None) -> str:
url = resolve_url(admin_urlname(obj._meta, SafeText("change")), obj.pk)
return format_html('{}', url, name or str(obj))
Then you can use the helper in your model-admin:
class MyAdmin(admin.ModelAdmin):
readonly_field = ["my_link"]
def my_link(self, obj):
return model_admin_url(obj.my_foreign_key)
I needed this for a lot of my admin pages, so I created a mixin for it that handles different use cases:
pip install django-admin-relation-links
Then:
from django.contrib import admin
from django_admin_relation_links import AdminChangeLinksMixin
#admin.register(Group)
class MyModelAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
# ...
change_links = ['field_name']
See the GitHub page for more info. Try it out and let me know how it works out!
https://github.com/gitaarik/django-admin-relation-links
I decided to make a simple admin mixin that looks like this (see docstring for usage):
from django.contrib.contenttypes.models import ContentType
from django.utils.html import format_html
from rest_framework.reverse import reverse
class RelatedObjectLinkMixin(object):
"""
Generate links to related links. Add this mixin to a Django admin model. Add a 'link_fields' attribute to the admin
containing a list of related model fields and then add the attribute name with a '_link' suffix to the
list_display attribute. For Example a Student model with a 'teacher' attribute would have an Admin class like this:
class StudentAdmin(RelatedObjectLinkMixin, ...):
link_fields = ['teacher']
list_display = [
...
'teacher_link'
...
]
"""
link_fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.link_fields:
for field_name in self.link_fields:
func_name = field_name + '_link'
setattr(self, func_name, self._generate_link_func(field_name))
def _generate_link_func(self, field_name):
def _func(obj, *args, **kwargs):
related_obj = getattr(obj, field_name)
if related_obj:
content_type = ContentType.objects.get_for_model(related_obj.__class__)
url_name = 'admin:%s_%s_change' % (content_type.app_label, content_type.model)
url = reverse(url_name, args=[related_obj.pk])
return format_html('{}', url, str(related_obj))
else:
return None
return _func
If anyone is trying to do this with inline admin, consider a property called show_change_link since Django 1.8.
Your code could then look like this:
class QuestionInline(admin.TabularInline):
model = Question
extra = 1
show_change_link = True
class TestAdmin(admin.ModelAdmin):
inlines = (QuestionInline,)
admin.site.register(Test, TestAdmin)
This will add a change/update link for each foreign key relationship in the admin's inline section.

Override Django Object Serializer to get rid of specified model

I need to convert a Django Queryset Object into a Json string. The built in Django Serialization library works great. Although it specifies the name of the Model from where it was created. Since I don't need this, how do I get rid of it? What else do I need to override to be able to use the overridden end_object method below?
class Serializer(PythonSerializer):
def end_object(self, obj):
self.objects.append({
"model" : smart_unicode(obj._meta), # <-- I want to remove this
"pk" : smart_unicode(obj._get_pk_val(), strings_only=True),
"fields" : fields
})
self._current = None
Sorry I had totally forgot about this question. This is how I ended up solving it (with thanks to FunkyBob on #django):
from django.core.serializers.python import Serializer
class MySerialiser(Serializer):
def end_object( self, obj ):
self._current['id'] = obj._get_pk_val()
self.objects.append( self._current )
# views.py
serializer = MySerialiser()
data = serializer.serialize(some_qs)
Here's a serializer that removes all metadata (pk, models) from the serialized output, and moves the fields up to the top-level of each object. Tested in django 1.5
from django.core.serializers import json
class CleanSerializer(json.Serializer):
def get_dump_object(self, obj):
return self._current
Serializer = CleanSerializer
Edit:
In migrating my app to an older version of django (precipitated by a change in hosting provider), the above serializer stopped working. Below is a serializer that handles the referenced versions:
import logging
import django
from django.core.serializers import json
from django.utils.encoding import smart_unicode
class CleanSerializer148(json.Serializer):
def end_object(self, obj):
current = self._current
current.update({'pk': smart_unicode(obj._get_pk_val(),
strings_only=True)})
self.objects.append(current)
self._current = None
class CleanSerializer151(json.Serializer):
def get_dump_object(self, obj):
self._current['pk'] = obj.pk
return self._current
if django.get_version() == '1.4.8':
CleanSerializer = CleanSerializer148
else:
CleanSerializer = CleanSerializer151
Serializer = CleanSerializer
Override JSON serializer class:
from django.core.serializers.json import Serializer, DjangoJSONEncoder
from django.utils import simplejson
class MySerializer(Serializer):
"""
Convert QuerySets to JSONS, overrided to remove "model" from JSON
"""
def end_serialization(self):
# little hack
cleaned_objects = []
for obj in self.objects:
del obj['model']
cleaned_objects.append(obj)
simplejson.dump(cleaned_objects, self.stream, cls=DjangoJSONEncoder, **self.options)
In the view:
JSONSerializer = MySerializer
jS = JSONSerializer()

Restricting admin model entry

I have a such model named Foo:
class Foo(models.Model):
name = models.CharField()
entry = models.DateField()
I have 2 types of users who can login to the admin panel, regular and superusers. I want to disallow the edition/deletion of Foo entries that are older than 2 days (by using the entry date field) but superusers may edit without any restriction.
How can I achieve this?
Thanks
Override ModelAdmin's queryset and has_change_permission :
from django.contrib.admin import ModelAdmin
class FooAdmin(ModelAdmin):
def has_change_permission(self, request, obj):
return obj is None or self.queryset(request).filter(pk=obj.pk).count() > 0
def queryset(self, request):
query = super(FooAdmin, self).queryset(request)
if request.user.is_superuser:
return query
else:
from datetime import datetime, timedelta
return query.filter(entry__gt=datetime.now()-timedelta(days=2))
admin.site.register(Foo, FooAdmin)
has_change_permission is used by the change view, and queryset by the list view. I reuse the queryset inside the overridden has_change_permission to stay DRY ( so you can add additional filters in queryset without caring to add this logic in the change permission check ), but it's worth mentioning that it costs an additional query.