Django Using Slug Field for Detail URL - django

I'm attempting to setup my site so that the url for my job-detail will use a slug field instead of a pk. It's telling me that it cannot find my job with the given slug (which is an int, 147).
Update:
After looking at the DetailView description at https://ccbv.co.uk/projects/Django/1.11/django.views.generic.detail/DetailView/ I realized there is a slug_field attribute for DetailView. My new view looks like:
class JobDetailView(CacheMixin, DetailView):
model = Job
slug_field = 'slug'
Question:
urls:
urlpatterns = [
url(r'^careers$', views.job_list, name='job-list'),
url(r'^careers/(?P<slug>[0-9]+)/$', views.JobDetailView.as_view(), name='job-detail'),
]
view:
class JobDetailView(CacheMixin, DetailView):
model = Job
pk_url_kwarg = 'slug'
def get_object(self, *args, **kwargs):
# Call the superclass
object = super(JobDetailView, self).get_object()
# Return the object
return object
def get(self, request, *args, **kwargs):
object = super(JobDetailView, self).get(request, *args, **kwargs)
return object
model:
class Job(UpdateAble, PublishAble, models.Model):
slug = models.CharField(unique=True, max_length=25)
facility = models.ForeignKey('Facility')
recruiter = models.ForeignKey('Recruiter')
title = models.TextField()
practice_description = models.TextField(blank=True, default="")
public_description = models.TextField(blank=True, default="")
objects = JobManager()
def get_next(self, **kwargs):
jobs = Job.objects.published()
next = next_in_order(self, qs=jobs)
if not next:
next = jobs[0]
return next
def get_prev(self, **kwargs):
jobs = Job.objects.published()
prev = prev_in_order(self, qs=jobs)
if not prev:
prev = jobs[len(jobs)-1]
return prev
def __str__(self):
return f'{self.facility}; {self.title}'
manager:
class JobManager(models.Manager):
def published(self):
return super(JobManager, self).get_queryset().filter(is_published=True).order_by('facility__name', 'title')

You actually don't need to define pk_url_kwarg at all, and in fact by doing so you have confused things leading to the object not being found.
As you can see from the default implementation of get_object, the view normally looks for either a pk or slug kwarg in the URL; whichever it finds will be used for the lookup. But by setting pk_url_kwarg to slug, you're telling the view to get the URL kwarg named "slug" but use it to look up against the PK field, which obviously won't work.
Just remove that attribute altogether, and Django will detect your slug kwarg and use it to correctly look up against the slug field.

Related

filtering relational models inside django forms

i have a model which has a foreign key relation with two oder models one of them is 'level'.
the view knows in which level you are based on a session variable,
and then filter the lessons
this is the lesson model:
class Lesson(models.Model):
level = models.ForeignKey(Level,on_delete=models.CASCADE)
subject = models.ForeignKey(Subject,on_delete=models.CASCADE)
chapiter = models.CharField(max_length=200)
lesson = models.CharField(max_length=200)
skill = models.CharField(max_length=200)
vacations = models.IntegerField()
link = models.URLField(max_length=700,null=True,blank=True)
remarques = models.TextField(null=True,blank=True)
order = models.IntegerField()
created = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now=True)
state = models.BooleanField(default=False)
now this is my cbv to create a new lesson:
class GlobalLessonView(CreateView):
model = Lesson
form_class = GlobalLessonForm
success_url = reverse_lazy('globalform')
and this is the form:
class GlobalLessonForm(forms.ModelForm):
class Meta:
model = Lesson
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['subject'].queryset = Subject.objects.none() #change to .all() to see list of all subjects
if 'level' in self.data:
try:
level_id = int(self.data.get('level'))
self.fields['subject'].queryset = Subject.objects.extra(where=[db_name+'.scolarité_subject.id in( select subject_id from '+db_name+'.scolarité_levelsubject where level_id='+level_id+')'])
except (ValueError, TypeError):
pass # invalid input from the client; ignore and fallback to empty City queryset
elif self.instance.pk:
self.fields['subject'].queryset = self.instance.level.subject_set
one of the main conditions is to filter level by a session variable
but the form does not accept request.session
so is there any way to change the levels that shows up at the form from the class based view,or there any way to pass request.session to form.py
Add this to GlobalLessonView:
def get_form_kwargs(self):
"""Pass request to form."""
kwargs = super().get_form_kwargs()
kwargs.update(request=self.request)
return kwargs
Then change the constructor definition in GlobalLessonForm to:
def __init__(self, request, *args, **kwargs):
Then you will be able to reference request.session in GlobalLessonForm.

Django DetailView with few url parameters

Let's say I have next models:
class Category(models.Model):
cat_name = models.CharField(u'name',max_length=50, unique=True)
slug = models.SlugField(u'URL',unique=True)
class News(models.Model):
title = models.CharField(max_length=250)
slug = models.SlugField(unique=True)
category = models.ForeignKey(Category,null=True,on_delete=models.SET_NULL)
And my get_absolute_url function looks like that:
def get_absolute_url(self):
return ('news:detail',(), {'slug':self.slug})
And url looks like /article/<slug>
If I want to have URL like <category.slug><news.slug> ? How can I do that?
You can do that:
# models.py
def get_absolute_url(self):
return ('news:detail', (), {
'category_slug': self.category.slug,
'slug': self.slug,
})
# views.py
class NewsDetail(DetailView):
model = News
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_queryset(self):
return News.objects.filter(category__slug=self.kwargs['category_slug'])
# urls.py
url(r'(?P<category_slug>[a-z0-9\-]+)/(?P<slug>[a-z0-9\-]+)/$', NewsDetail.as_view(), name='news')
So the idea is to add 2 parameters to your url definition. Last one is usual to query a news row and the first one will used by get_queryset to filter the news by the specified category.
More about parameters in urls in docs - https://docs.djangoproject.com/en/1.9/topics/http/urls/#named-groups

Foreign key lookups - slug URL with Django generic list view

I have been searching here and reading the documentation and experimenting in python, but I can't find a solution to my particular mess. I did the Django tutorial, but I'm still confused as to how to pass stuff thru the URL in django when starting to use foreign keys, I'm sure it's pretty simple. I'm a new django user and this is my first post. I have the models Playlist, and PlaylistEntry with mixed relationships to user and videos (not posted). I'm trying to show a detail view that uses a slug of a playlist title, to pull out entries in the playlist. Eg. In python, I can do
entries = PlaylistEntry.objects.filter(playlist__slug='django')
which returns all my entries in the playlist 'Django' correctly.
Here are my models...
class Playlist(models.Model):
class Meta:
verbose_name_plural = 'playliztz'
title = models.CharField(blank=True,
help_text=u'Title of playlist',
max_length=64,
)
user = models.ForeignKey('userprofile.UserProfile',
blank=False,
help_text=u'owns this playlist',
null=False, )
slug = models.SlugField(u'slug',
max_length=160,
blank=True,
editable=False
)
created = models.DateTimeField(editable=False)
modified = models.DateTimeField(editable=False)
def __unicode__(self):
return self.title
def get_absolute_url(self):
return reverse('playlist-detail', kwargs={'pk': self.pk})
def save(self):
self.slug = slugify(self.title)
if not self.id:
self.created = datetime.datetime.today()
self.modified = datetime.datetime.today()
super(Playlist,self).save()
class PlaylistEntry(models.Model):
class Meta:
verbose_name_plural = "playlist entries"
video = models.ForeignKey('video.Video',
blank=True,
default=None,
help_text=u'the video title',
null=True, )
playlist = models.ForeignKey('Playlist',
blank=True,
default=None,
help_text=u'Belongs to this playlist',
null=True,)
def __unicode__(self):
return self.video.video_title
My URLS looks like this...
url(r'^playlist/$', PlaylistList.as_view(), name='user_playlists'),
url(r'^playlist/(?P<slug>[0-9A-Za-z-_.//]+)/$', PlaylistDetail.as_view(), name='playlist_entries'),
And my Views.py looks like this...
class PlaylistList(LoggedInMixin, ListView): # shows a list of playlists
template_name = 'userprofile/playlist_list.html'
model = Playlist
context_object_name = 'playlist_list'
def get_queryset(self):
"""Return currently users playlists."""
return Playlist.objects.filter(user__user=self.request.user)
def get_context_data(self, **kwargs):
context = super(PlaylistList, self).get_context_data(**kwargs)
if not self.get_queryset():
context['error'] = "You don't have any playlists yet."
return context
else:
return context
class PlaylistDetail(LoggedInMixin, DetailView):
model = PlaylistEntry
template_name = 'userprofile/playlist_detail.html'
def get_queryset(self):
self.current_playlist = get_object_or_404(Playlist, slug=self.kwargs['slug'])
# CHECK - Prints out correct entries for playlist in slugfield (current_playlist)
self.entries = PlaylistEntry.objects.filter(playlist__title=self.current_playlist.title)
print self.entries
# Should expect to return the same queryset?
return PlaylistEntry.objects.filter(playlist__title=self.current_playlist.title)
def get_context_data(self, **kwargs):
context = super(PlaylistEntry, self).get_context_data(**kwargs)
context['entries'] = PlaylistEntry.objects.all()
return context
self.entries prints the correct entries for this playlist in the Check bit.
In my playlist template I am using a link sending the playlist.slug - the url looks correct like this /user/playlist/this-particular-playlist-slug.
the error is...
Cannot resolve keyword u'slug' into field. Choices are: id, playlist, video
You've made things much more complicated than they need to be.
The model for your detail view should still be Playlist, not PlaylistEntry. The reason you're getting that error is that the slug is on the Playlist model, but you've told the view to filter on PlaylistEntry.
What you actually want to to is to pass the single Playlist identified by the slug into the template. From there, you can easily iterate through the detail objects associated with that playlist via the reverse relation.
So, change that model setting, and drop both get_context_data and get_queryset from the detail view: you don't need them. Then in the template you can simply do:
{% for entry in playlist.playlistentry_set.all %}

Using Django-taggit with django-rest-framework, i'm not able to save my tags

I'm trying to figure it out why when i submit my form, my tags are not saved in my db. Pretty new with the django-rest-framework and Django-taggit too, i think i'm doing something wrong :)
First, before making my API with the rest-framework, i was using a generic view (CreateView and UpdateView) to register/validate my event. It was working fine but i decided to go further and try to build an API since i'm using Angularjs now.
Now my model event is created but without my tag and i have some errors. I put some code and i'll describe my errors after.
events/models.py
class Event(models.Model):
[...]
title = models.CharField(max_length=245, blank=False)
description = models.TextField(max_length=750, null=True, blank=True)
start = models.DateTimeField()
end = models.DateTimeField()
created_at = models.DateTimeField(editable=False)
updated_at = models.DateTimeField(editable=False)
slug = AutoSlugField(populate_from='title', unique=True, editable=False)
expert = models.BooleanField(choices=MODE_EXPERT, default=0)
home = models.BooleanField(choices=HOME, default=0)
nb_participant = models.PositiveSmallIntegerField(default=1)
price = models.PositiveSmallIntegerField(default=0)
cancelled = models.BooleanField(default=0)
user = models.ForeignKey(User, editable=False, related_name='author')
address = models.ForeignKey('Address', editable=False, related_name='events')
participants = models.ManyToManyField(User, related_name='participants', blank=True, editable=False,
through='Participants')
theme_category = models.ForeignKey('EventThemeCategory', unique=True, editable=False)
tags = TaggableManager(blank=True)
class Meta:
db_table = 'event'
def save(self, *args, **kwargs):
if not self.pk:
self.created_at = timezone.now()
self.updated_at = timezone.now()
super(Event, self).save(*args, **kwargs)
[...]
i'm using the serializers.HyperlinkedModelSerializer.
api/serializer.py
from taggit.models import Tag
class TagListSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Tag
fields = ('url', 'id', 'name')
class EventSerializer(serializers.HyperlinkedModelSerializer):
address = AddressSerializer()
user = UserSerializer(required=False)
tags = TagListSerializer(blank=True)
class Meta:
model = Event
fields = ('url', 'id', 'title', 'description', 'start', 'end', 'created_at', 'updated_at', 'slug', 'expert','home', 'nb_participant', 'price', 'address', 'user', 'theme_category', 'tags')
depth = 1
api/views/tags_views.py
from rest_framework import generics
from api.serializers import TagListSerializer
from taggit.models import Tag
class TagsListAPIView(generics.ListCreateAPIView):
queryset = Tag.objects.all()
model = Tag
serializer_class = TagListSerializer
class TagsDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Tag.objects.all()
model = Tag
serializer_class = TagListSerializer
api/views/events_views.py
class EventListAPIView(generics.ListCreateAPIView):
queryset = Event.objects.all()
model = Event
serializer_class = EventSerializer
paginate_by = 100
def pre_save(self, obj):
"""
Set the object's owner, based on the incoming request.
"""
obj.user = self.request.user
return super(EventListAPIView, self).pre_save(obj)
api/urls.py
url(r'^events/(?P<slug>[0-9a-zA-Z_-]+)/$', EventDetailAPIView.as_view(), name='event-detail'),
So first when i call /api/events/name-of-my-event the API send me the good resource with my tags on it. The GET method is working fine.
I was thinking that rest-framework follow the query set. So if i can get the resource with with all my tags why when i use POST my tags are not register ?
Actually i have two problems with the POST method:
first one if i send a tag which i have already created, he send me an error saying that the tag must be unique. I understand that, i don't want to create a new one, i just want it to be linked with my object. I don't have this problem when i use the generic view (it's done by magic :) and all is working fine)
Secondly, when i try to create a new tag, my new event is saved but without my tags.
You can see the response received by angularjs for my tag... He send me the name of the tag but without id, url (hyperlinked). When i checked my db the tag has not been created.
I think i have to make a custom get_queryset(self) in my tags_views but i'm not sure.
I'll will continue to investigate. If someone have already to that and have some advise, i'll be very API. Thanks.
meet the same question. But I just want to save the tag list directly by TaggableManager (without TagListSerializer and TagsListAPIView). My solution is:
class MyModel(models.Model):
...
tags = TaggableManager(blank=True)
def get_tags_display(self):
return self.tags.values_list('name', flat=True)
class MyModelSerializer(serializers.HyperlinkedModelSerializer):
...
tags = serializers.Field(source='get_tags_display') # more about: http://www.django-rest-framework.org/api-guide/fields#generic-fields
...
class MyModelViewSet(viewsets.ModelViewSet):
...
def post_save(self, *args, **kwargs):
if 'tags' in self.request.DATA:
self.object.tags.set(*self.request.DATA['tags']) # type(self.object.tags) == <taggit.managers._TaggableManager>
return super(MyModelViewSet, self).post_save(*args, **kwargs)
The post data of tags data will be ['tagA', 'tagB',...], the TaggableManager will handle it. Thx.
For DRF>3.1, you just need to override create and update in your ModelSerializer class:
class StringListField(serializers.ListField): # get from http://www.django-rest-framework.org/api-guide/fields/#listfield
child = serializers.CharField()
def to_representation(self, data):
return ' '.join(data.values_list('name', flat=True)) # you change the representation style here.
class MyModelSerializer(serializers.ModelSerializer):
tags = StringListField()
class Meta:
model = models.MyModel
def create(self, validated_data):
tags = validated_data.pop('tags')
instance = super(MyModelSerializer, self).create(validated_data)
instance.tags.set(*tags)
return instance
def update(self, instance, validated_data):
# looks same as create method
I used to follow the following ways to serialize taggit objects but currently django-taggit provide a built in serializer https://github.com/jazzband/django-taggit/blob/master/taggit/serializers.py and it was vendor from the package I mentioned previously.
"""
Django-taggit serializer support
Originally vendored from https://github.com/glemmaPaul/django-taggit-serializer
"""
import json
# Third party
from django.utils.translation import gettext_lazy
from rest_framework import serializers
class TagList(list):
def __init__(self, *args, **kwargs):
pretty_print = kwargs.pop("pretty_print", True)
super().__init__(*args, **kwargs)
self.pretty_print = pretty_print
def __add__(self, rhs):
return TagList(super().__add__(rhs))
def __getitem__(self, item):
result = super().__getitem__(item)
try:
return TagList(result)
except TypeError:
return result
def __str__(self):
if self.pretty_print:
return json.dumps(self, sort_keys=True, indent=4, separators=(",", ": "))
else:
return json.dumps(self)
class TagListSerializerField(serializers.Field):
child = serializers.CharField()
default_error_messages = {
"not_a_list": gettext_lazy(
'Expected a list of items but got type "{input_type}".'
),
"invalid_json": gettext_lazy(
"Invalid json list. A tag list submitted in string"
" form must be valid json."
),
"not_a_str": gettext_lazy("All list items must be of string type."),
}
order_by = None
def __init__(self, **kwargs):
pretty_print = kwargs.pop("pretty_print", True)
style = kwargs.pop("style", {})
kwargs["style"] = {"base_template": "textarea.html"}
kwargs["style"].update(style)
super().__init__(**kwargs)
self.pretty_print = pretty_print
def to_internal_value(self, value):
if isinstance(value, str):
if not value:
value = "[]"
try:
value = json.loads(value)
except ValueError:
self.fail("invalid_json")
if not isinstance(value, list):
self.fail("not_a_list", input_type=type(value).__name__)
for s in value:
if not isinstance(s, str):
self.fail("not_a_str")
self.child.run_validation(s)
return value
def to_representation(self, value):
if not isinstance(value, TagList):
if not isinstance(value, list):
if self.order_by:
tags = value.all().order_by(*self.order_by)
else:
tags = value.all()
value = [tag.name for tag in tags]
value = TagList(value, pretty_print=self.pretty_print)
return value
class TaggitSerializer(serializers.Serializer):
def create(self, validated_data):
to_be_tagged, validated_data = self._pop_tags(validated_data)
tag_object = super().create(validated_data)
return self._save_tags(tag_object, to_be_tagged)
def update(self, instance, validated_data):
to_be_tagged, validated_data = self._pop_tags(validated_data)
tag_object = super().update(instance, validated_data)
return self._save_tags(tag_object, to_be_tagged)
def _save_tags(self, tag_object, tags):
for key in tags.keys():
tag_values = tags.get(key)
getattr(tag_object, key).set(tag_values)
return tag_object
def _pop_tags(self, validated_data):
to_be_tagged = {}
for key in self.fields.keys():
field = self.fields[key]
if isinstance(field, TagListSerializerField):
if key in validated_data:
to_be_tagged[key] = validated_data.pop(key)
return (to_be_tagged, validated_data)
http://blog.pedesen.de/2013/07/06/Using-django-rest-framework-with-tagged-items-django-taggit/
With the release of the Django Rest Framework 3.0, the code for the TagListSerializer has changed slightly. The serializers.WritableField was depreciated in favour for serializers.Field for the creation of custom serializer fields such as this. Below is the corrected code for Django Rest Framework 3.0.
class TagListSerializer(serializers.Field):
def to_internal_value(self, data):
if type(data) is not list:
raise ParseError("expected a list of data")
return data
def to_representation(self, obj):
if type(obj) is not list:
return [tag.name for tag in obj.all()]
return obj
I now use the bulit in taggit serializer which was taken from https://github.com/glemmaPaul/django-taggit-serializer library.
I had a bunch of errors but i found a way to resolve my problem. Maybe not the best as i'm pretty new with all of this but for now it works.
I'll try to describe all my errors maybe it'll help someone.
First my angularjs send a json which match exatly the queryset
So for example with my model events below, angularjs send to the API:
Now let's begin with all my errors:
"A tag with this name already exist"
When i re-use a tag i have this error. Don't know why because with a classic validation without the API, all is working fine.
With a new tag nothing is saved too.
When i try to use a new tag on my event event model nothing is saved on the database. Angularjs received a response with the tag name but with an id of null (see the pitcure on my original question)
"AttributeError: 'RelationsList' object has no attribute 'add'"
Now i'm trying to think that to register my tags i need to have an instance of event already created. Thanks to that i will be able to add my tag on it like it's describe in the doc.
apple.tags.add("red", "green", "fruit")
So i decided to add a post_save in my events_views.py:
class EventListAPIView(generics.ListCreateAPIView):
queryset = Event.objects.all()
model = Event
serializer_class = EventSerializer
paginate_by = 100
def pre_save(self, obj):
"""
Set the object's owner, based on the incoming request.
"""
obj.user = self.request.user
return super(EventListAPIView, self).pre_save(obj)
def post_save(self, obj, created=False):
print 'tags', self.request.DATA
obj.tags.add(self.request.DATA['tags'])
return super(EventListAPIView, self).post_save(obj)
But now as is said i have this error AttributeError: 'RelationsList' object has no attribute 'add'.
Actually, it's obvious since obj.tags is a list of object and not the TaggableManager anymore.
So i decided to start over and send my tags not in 'tags' but in another custom property 'tagged' to avoid conflit with the TaggableManager.
"TypeError: unhashable type: 'list'"
New error :) I found the solution with this django-taggit-unhashable-type-list
def post_save(self, obj, created=False):
map(obj.tags.add, self.request.DATA['tagged'])
return super(EventListAPIView, self).post_save(obj)
"TypeError: unhashable type: 'dict'"
Now, i figured it out that the tags i sent are not well formatted. I changed it (on the angularjs side) to send an array like this ['jazz','rock'] instead of [object, object]. Stupid mistake from a beginner.
Now the magic happen, response received by angularjs is good:
Sorry for my english. I know it may not be the best solution and i will try to update it when i'll find another solution.

How do I write a write a view in django that gets an object_list based on a field?

I have a simple tag model and a simple project model.
In the project model I have a m2m to the tag model.
I want to return all the projects with a tag. I'm almost there.
Right now the view below returns invalid literal for int() with base 10: 'cheap'
So, it has the right slug, and it's making the query, but it's trying to get the list of projects based on the id of the m2m tag.
Any suggestion much appreciated.
My Tag Model:
class Tag(models.Model):
"""
A basic tag model for projects
"""
name = models.CharField(max_length=100, unique=True)
slug = models.CharField(max_length=100)
description = models.TextField(blank=True)
class Meta:
ordering = ('name',)
verbose_name = _('Tag')
verbose_name_plural = _('Tags')
def __unicode__(self):
return self.name
#models.permalink
def get_url_path(self):
return ('TagDetail', (), {'slug': self.slug})
My url:
# tags/<slug>/ The detail view for an archived project
url(regex=r'^tags/(?P<slug>[\w-]+)/$',
view=TagDetail.as_view(),
name='tag_detail',
),
My view I'm trying to figure out:
class TagDetail(ListView):
""" Get all projects for a tag """
template_name = "projects/TagDetail.html"
def get_queryset(self):
tags = get_list_or_404(Project, tags=self.kwargs['slug'], displayed=True)
paginate_by = 10
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(TagDetail, self).dispatch(*args, **kwargs)
Assuming your Project model looks like this
class Project( models.Model ):
tags=models.ManyToManyField( Tag )
match to the tag's slug
def get_queryset( self ):
return get_list_or_404(Project, tags__slug=self.kwargs['slug'], displayed=True)
the only change being tags__slug.