Trouble using "Slug" in Django DetailView - django

models.py
class Tag(models.Model):
name = models.CharField(max_length=64, unique=True)
slug = models.SlugField(max_length=255, unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Tag, self).save(*args, **kwargs)
urls.py
url(r'^tag/(?P<slug>[A-Za-z0-9_\-]+)/$', TagDetailView.as_view(), name='tag_detail'),
views.py
class TagDetailView(DetailView):
template_name = 'tag_detail_page.html'
context_object_name = 'tag'
Well, I thought this would work without any problem, because Django's generic DetailView will look for "slug" or "pk" to fetch its object. However, navigating to "localhost/tag/RandomTag" gives me an error:
error:
ImproperlyConfigured at /tag/RandomTag/
TagDetailView is missing a queryset. Define TagDetailView.model, TagDetailView.queryset, or override TagDetailView.get_queryset().
Does anyone know why this is happening...???
Thanks!!!

because Django's generic DetailView will look for "slug" or "pk" to fetch its object
It will, but you haven't told it what model it is to use. The error is very clear about this:
Define TagDetailView.model, TagDetailView.queryset, or override TagDetailView.get_queryset().
You can use the model or queryset attributes to do this, or the get_queryset() method:
class TagDetailView(...):
# The model that this view will display data for.Specifying model = Foo
# is effectively the same as specifying queryset = Foo.objects.all().
model = Tag
# A QuerySet that represents the objects. If provided,
# the value of queryset supersedes the value provided for model.
queryset = Tag.objects.all()
# Returns the queryset that will be used to retrieve the object that this
# view will display. By default, get_queryset() returns the value of the
# queryset attribute if it is set, otherwise it constructs a QuerySet by
# calling the all() method on the model attribute’s default manager.
def get_queryset():
....
There are a few different ways of telling the view where you want it to grab your object from, so have a read of the docs for more

Related

DRF: values() does not return groupby object

Suppose I have:
# models.py
class Project(models.Model):
project = models.CharField(max_length=200)
subproject = models.CharField(max_length=200)
physical_pct = models.FloatField()
cost = models.FloatField()
# serializers.py
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = '__all__'
In my viewset, I want to display a grouped by object by name that will later be annotated. I referred to this example from values.
# views.py
class ProjectsViewSet(viewsets.ModelViewSet):
serializer_class = ProjectSerializer
def get_queryset(self):
queryset = Project.objects.values('project')
print(queryset)
return queryset
When I print queryset it displays a list of all project without the other fields in the terminal. However it raises an error:
"Got KeyError when attempting to get a value for field `subproject` on serializer `ProjectSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `dict` instance.\nOriginal exception text was: 'subproject'."
My desired output is a json grouped by the project field.
UPDATE 1:
It will not have an error if I put all fields in the values() arguments i.e.
.values('project', 'subproject', 'physical_pct', 'cost',)
Which now then destroy the purpose of values being grouped by.
Main cause of the problem is for your serializer defination. If you only want to return back project field response make sure you are using appropriate serializer for that.
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = '__all__' // this means you are suppose to pass all model fields
So we need custom serializer for this purpose
class ProjectListSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('project',)
And also we need to update get_serializer method according to our necessary need
# views.py
class ProjectsViewSet(viewsets.ModelViewSet):
def get_queryset(self):
// If you are doing so you are suppose to have only one 'project' field response
queryset = Project.objects.values('project')
return queryset
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrive':
return ProjectListSerializer
return ProjectSerializer
Try:
queryset = Project.objects.all().values('project')
You are querying only project field in Project model but trying to serialize all field in Project model. If you want to serialize one field, you don't need the serializer:
class ProjectsViewSet(viewsets.ModelViewSet):
serializer_class = ProjectSerializer
queryset = Project.objects.all()
def list(self, request, *args, **kwargs):
qs = self.get_queryset().values_list('project', flat=True)
return Response(qs)

Use serializer validated data in get_queryset()

I want to write custom get_queryset() method for serializer based on query params.
Here is my serializer:
class SearchRequestSerializer(serializers.Serializer):
name = serializers.CharField(max_length=255, required=False)
nickname = serializers.RegexField(
r'^(?!(.*?\_){2})(?!(.*?\.){2})[A-Za-z0-9\._]{3,24}$',
max_length=24,
min_length=3,
required=False,
)
modelA_id = serializers.CharField(max_length=11, min_length=11,
required=False)
def validate_modelA_id(self, value):
queryset = modelA.objects.filter(id=value)
if queryset.exists():
return queryset.first()
else:
raise serializers.ValidationError(_('Not found'))
If object of modelA exists - validation will return an instance. But I
don't want to perform the same query in get_queryset() in my if branch.
def get_queryset(self):
name = self.request.query_params.get('name', None)
nickname = self.request.query_params.get('nickname', None)
queryset = User.objects.filter(Q(name__contains=name)|Q(nickname__contains=nickname))
if 'modelA_id' in self.request.query_params:
# in this case will be annotated extra field to queryset
# extra field will be based on 'modelA' instance which should be returned by serializer
return queryset
I found only one solution - add the following line in my GET method:
self.serializer = self.get_serializer()
Than it will be possible to get validated values in my get_queryset() method. But PyCharm don't like this solution
I'm under strong impression that you are misusing the Serializer. After quick analysis of your issue i think you need DRF filtering
Serializers process request.data which under the hood is just Django request.POST and request.FILES yet in your get_queryset implementation you make lookups in request.query_params which in Django terms is request.GET. Check the DRF docs on this.
In order to achieve what you need with Serializers you would have to abuse the Views, Serializer Fields and Serializer itself. It is simply not what its supposed to do.
Additionaly I don't think that you need so much validation on search.
Customization and use of Django Filter should solve your problem.
class UserFilter(filters.FilterSet):
class Meta:
model = User
fields = ['name', 'nickname', 'modelA_id']
def filter_queryset(self, queryset):
name = self.form.cleaned_data.get('name', None)
nickname = self.form.cleaned_data.get('nickname', None)
queryset = queryset.filter(Q(name__contains=name)|Q(nickname__contains=nickname))
if 'modelA_id' in self.form.cleaned_data:
# in this case will be annotated extra field to queryset
# extra field will be based on 'modelA' instance which should be returned by serializer
return queryset
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
filterset_class = UserFilter
NOTE I did not test code above but it should show you how to tackle this problem.
How is about user = get_object_or_404(queryset, id=ModelA_id). It looks better as for me.
get_object_or_404 will catch an object you need, or will raise Not Found responce.

UpdateView without a PK or Slug

Having trouble with an UpdateView. I've tried over writing the get_object but I am getting
AttributeError at /companydata/update/
'User' object has no attribute 'get_companydata'
The CompanyData Model has a OneToOne relationship with User.
Here's my code:
urls.py
### Omitted ###
url(r'^update/$', CompanyDataUpdateView.as_view(),
name='companydataupdate')
### Omitted ###
views.py
class CompanyDataUpdateView(UpdateView):
model = CompanyData
fields = ['arr', 'num_cust']
template_name = 'company_data/companydata_form.html'
def get_object(self):
return self.request.user.get_companydata()
models.py
class CompanyData(models.Model):
user = models.OneToOneField(User)
arr = models.DecimalField(max_digits=20, decimal_places=2, validators=[MinValueValidator(1)])
num_cust = models.IntegerField(validators=[MinValueValidator(1)])
def get_absolute_url(self):
return reverse('companyrevenue')
Any help would be greatly apprecaited!
The User object has no method called get_companydata, hence your error. You need to access the reverse one-to-one relationship like so:
def get_object(self):
return self.request.user.companydata
Where companydata is a property, not a method (i.e., don't call it with brackets). This is the default reverse name for the one-to-one relationship:
If you do not specify the related_name argument for the OneToOneField, Django will use the lower-case name of the current model as default value.
If you want to be more explicit or use another name, then set the related_name on your OneToOneField.

Django Rest Framework Nested Routes - PK alternatives

I am using drf-nested-routers to nest my resources and everything is working well. I would like, however, to use something other than the pk to refer to a parent object.
What I currently have is:
api/movies/4/scenes - generates a list of scenes from movie with pk=4.
What I would like is:
api/movies/ghost-busters/scenes - where the identifier is movie.title instead of movie.pk
Any suggestions?
Thanks
you can use slug for the url you want to make "api/movies/ghost-busters/scenes"
at first you have to make model with slugField eg.
class Blog(models.Model):
qoute = models.CharField(max_length=30)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.qoute)
super(Blog, self).save(*args, **kwargs)
during the saving model it will create a slug by "qoute" and save to the column "slug"
make your urls.py entry
url(r'^api/movies/(?P<slug>[\w-]+)/scenes/$', 'myapp.views.blog_detail', name='blog_detail'),
then for the drf you have set lookup_field in the serializer and view also.
N.B: you can user ModelSerializer or Serializer or HyperlinkSerialzer as you wish..
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ('quote', 'slug',)
lookup_field = 'slug'
and the views..
class blog_detail(generics.RetrieveUpdateDestroyAPIView):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
lookup_field = 'slug'

Get value of field from referenced model displayed in Django Admin site

I have two models - Contract and Supplier. Each supplier supplies a type of commodity. These are defined as follows:
class CommodityType(models.Model):
name = models.CharField(max_length=64)
def __unicode__(self):
return self.name
class Supplier(models.Model):
name = models.CharField(max_length=64)
type = models.ForeignKey(CommodityType)
def __unicode__(self):
return self.name
class Meta:
ordering = ['type', 'name']
class Contract(models.Model):
supplier = models.ForeignKey(Supplier)
clientNumber = models.CharField(max_length=32)
def __unicode__(self):
return u'%s, %s' % (self.supplier, self.clientNumber)
I want to have a listing of the Contracts in the Django Admin site. For each of the Contracts, I want to have the type from the referenced Supplier displayed. So, for example, if the associated supplier supplies Electricity, then I want to have that displayed in the listing of Contracts.
However, I cannot seem to find how this is done. I found this answer, but trying that gives me an ImproperlyConfigured error.
How can this be done?
What you probably need is the list_display
class ContractAdmin(admin.ModelAdmin):
list_display('clientNumber', 'supplier')
admin.register(Contract, ContractAdmin)
To allow __ in Admin for foreign key, You can use this snippet
From the snippet:
from django.contrib import admin
from django.db import models
def getter_for_related_field(name, admin_order_field=None, short_description=None):
"""
Create a function that can be attached to a ModelAdmin to use as a list_display field, e.g:
client__name = getter_for_related_field('client__name', short_description='Client')
"""
related_names = name.split('__')
def getter(self, obj):
for related_name in related_names:
obj = getattr(obj, related_name)
return obj
getter.admin_order_field = admin_order_field or name
getter.short_description = short_description or related_names[-1].title().replace('_',' ')
return getter
class RelatedFieldAdminMetaclass(admin.ModelAdmin.__metaclass__):
"""
Metaclass used by RelatedFieldAdmin to handle fetching of related field values.
We have to do this as a metaclass because Django checks that list_display fields are supported by the class.
"""
def __getattr__(self, name):
if '__' in name:
getter = getter_for_related_field(name)
setattr(self, name, getter) # cache so we don't have to do this again
return getter
raise AttributeError # let missing attribute be handled normally
class RelatedFieldAdmin(admin.ModelAdmin):
"""
Version of ModelAdmin that can use related fields in list_display, e.g.:
list_display = ('address__city', 'address__country__country_code')
"""
__metaclass__ = RelatedFieldAdminMetaclass
def queryset(self, request):
qs = super(RelatedFieldAdmin, self).queryset(request)
# include all related fields in queryset
select_related = [field.rsplit('__',1)[0] for field in self.list_display if '__' in field]
# Include all foreign key fields in queryset.
# This is based on ChangeList.get_query_set().
# We have to duplicate it here because select_related() only works once.
# Can't just use list_select_related because we might have multiple__depth__fields it won't follow.
model = qs.model
for field_name in self.list_display:
try:
field = model._meta.get_field(field_name)
except models.FieldDoesNotExist:
continue
if isinstance(field.rel, models.ManyToOneRel):
select_related.append(field_name)
return qs.select_related(*select_related)
#### USAGE ####
class FooAdmin(RelatedFieldAdmin):
# these fields will work automatically:
list_display = ('address__phone','address__country__country_code','address__foo')
# ... but you can also define them manually if you need to override short_description:
address__foo = getter_for_related_field('address__foo', short_description='Custom Name')
Recently, a library called django-related-admin released, which allows you to use foreign key attributes in Django admin change list list_display with '__' so easily, Specifically for this question, how to use this library in admin.py module is as follows:
admin.py
from related_admin import RelatedFieldAdmin
from related_admin import getter_for_related_field
class ContractAdmin(RelatedFieldAdmin):
# these fields will work automatically (and boolean fields will display an icon):
list_display = ('clientNumber','supplier__type__name')
# or you can also define them manually if you need to override short_description or boolean parameter:
supplierType = getter_for_related_field('supplier__type__name', short_description='supplier type', boolean=False)