I try to create nested categories with django-treebeard.
And in admin this solution work fine. But I need to show this categories. When I try reach root category eq.: /kategorie/wegetarianskie then works fine, but when I try to reach fullslug with children category eq.: /kategorie/wegetarianskie/obiady then I have error 404 and don't know why.
Maybe someone have answer.
My model:
class Category(MP_Node):
name = models.CharField(max_length=255, verbose_name='Nazwa kategorii')
slug = AutoSlugField(populate_from='name', unique=True)
fullslug = models.CharField(max_length=255, verbose_name='Pełny adres kategorii', blank=True)
created_at = models.DateTimeField(default=datetime.datetime.now)
node_order_by = ['name']
Then with signal I create full slug like this:
#receiver(post_save, sender=Category)
def save_parentname(sender, instance, **kwargs):
if not instance.fullslug:
if instance.is_root():
instance.fullslug = instance.slug
instance.save()
else:
catslug = '/'.join([instance.get_parent().slug, instance.slug])
instance.fullslug = catslug
instance.save()
When child category is save, then signal create full slug like: /kategorie/wegetarianskie/obiady
I have also get_absolute_url:
def get_absolute_url(self):
return reverse('categorydetail',
args=[self.fullslug])
View:
def category_detail(request, fullslug):
category = Category.objects.get(fullslug=fullslug)
return render(request, 'categories/category_detail.html', { 'category': category })
Urls:
urlpatterns = [
path('wszystkie', views.category_list, name='categorylist'),
path('<fullslug>/', views.category_detail, name='categorydetail'),
]
Related
I want to filter Blog Post objects or records based on the Post Category and a User that uploaded the Post record, it gives me an error when I try to do filter, this is the error.
ValueError at /dashboard/filter-post/
The QuerySet value for an exact lookup must be limited to one result using slicing.
Here is my models.py
class Category(models.Model):
cat_name = models.CharField(max_length=100, verbose_name='Category Name')
cat_desc = models.TextField(blank=True, null=True)
def __str__(self):
return self.cat_name
class Meta():
verbose_name_plural='Category'
class Post(models.Model):
pst_title = models.CharField(max_length=150)
pst_image = models.ImageField(blank=True, null=True, upload_to='uploads/')
user = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ManyToManyField(Category)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.pst_title
#property
def img_url(self):
if self.pst_image:
return self.pst_image.url
on forms.py
class FilterForm(forms.ModelForm):
user = forms.ModelChoiceField(
queryset=User.objects.all(),
widget=forms.Select(attrs={'class': 'form-control'}))
category = forms.ModelMultipleChoiceField(
queryset=Category.objects.all(),
widget=forms.SelectMultiple(attrs={'class': 'form-control js-example-disabled-results'}))
catch_bot = forms.CharField(required=False,
widget=forms.HiddenInput, validators=[validators.MaxLengthValidator(0)])
class Meta():
fields = ['user', 'category' ]
model = Post
on views.py
def filter_post(request):
post = FilterForm(request.GET)
queryset = Post.objects.all()
if post.is_valid():
user=post.cleaned_data.get('user')
category=post.cleaned_data.get('category')
if user and category:
queryset = queryset.filter(user__username=user, category__cat_name=category)
return render(request, 'backend/filter-post.html', {'query':queryset, 'post':post})
I am having challenges properly filtering this in my views any help?
Try this:
instead of this:
queryset = queryset.filter(user__username=user, category__cat_name=category)
use this:
queryset = queryset.filter(user=user, category=category)
Also don't name your model fields after the model name, just use name instead of pst_name or cat_name, you will see that when you will try access these values there will be no confusion.
UPDATE
Ok, maybe try to rewrite your view like this:
def filter_post(request):
posts = Post.objects.all()
form = FilterForm(request.GET) # its best practice to call your form instance `form` in the view so that the next line has better readability
if form.is_valid():
user=post.cleaned_data['user']
category=post.cleaned_data['category']
if user:
posts = posts.filter(user=user)
if category:
posts = posts.filter(category=category)
return render(request, 'backend/filter-post.html', {'posts':posts})
I want to create detail view for my Playlist model. I followed steps from docs and i keep getting this error:
AttributeError at /root
Generic detail view PlaylistDetail must be called with either an object pk or a slug in the URLconf.
here is my code:
model:
class Playlist(models.Model):
title = models.CharField(max_length=40, null=True)
description = models.CharField(max_length=500, null=True)
author = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
miniature = models.ImageField(upload_to='images/playlist', default="defaults/default.png", validators=[validate_miniature_file_extension])
tracks = models.ManyToManyField(Track)
def __str__(self):
return self.title
url:
path('<slug:author>', PlaylistDetail.as_view(), name='playlist'),
view:
class PlaylistDetail(DetailView):
model = Playlist
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
return context
I suppose its caused because of that there are more than one playlist created by the same user, and it will must get User && title. Any suggestions?
The issue is that in the urls, you use <slug:author>
path('<slug:author>', PlaylistDetail.as_view(), name='playlist'),
And by default it looks for slug.
Try updating the view with this:
class PlaylistDetail(DetailView):
model = Playlist
slug_url_kwarg = 'author' # <----
def get_context_data(self, **kwargs):
# . . .
Good afternoon, I am fairly new to Django and I am not sure how to go about this.I have a Django 2.2 project with these models:
class Equipment(models.Model):
name = models.CharField(
max_length=15,
unique=True,
verbose_name='asset name')
asset_cat = models.ForeignKey('category',on_delete=models.PROTECT,verbose_name='asset category')
asset_loc = models.ForeignKey('location',on_delete=models.PROTECT,verbose_name='asset location')
state = models.ForeignKey('status',on_delete=models.PROTECT,verbose_name='status')
brand = models.CharField(
max_length=15,
unique=False,
blank=True)
model = models.CharField(
max_length=12,
unique=False,
blank=True,
verbose_name='model number')
def __str__(self):
return "{}".format(self.name)
def get_absolute_url(self):
return reverse('equipment-detail', args=[str(self.id)])
class Meta:
ordering = ['asset_cat', 'name']
verbose_name_plural = 'pieces of equipment'
class Action(models.Model):
name = models.ForeignKey('equipment',on_delete=models.PROTECT,verbose_name='asset name',blank=False)
dt = models.DateTimeField(
auto_now_add=True,
verbose_name='date and time of incident')
incident = models.TextField(
blank=True,
null=True)
CHANGE = 'CHANGE'
SERVICE = 'SERVICE'
ACTION_CHOICES = (
(CHANGE, 'CHANGE'),
(SERVICE, 'SERVICE')
)
act = models.TextField(
blank=True,
choices=ACTION_CHOICES,
null=True,
verbose_name='action taken')
act_detail = models.TextField(
verbose_name='action detail',
blank=False)
result = models.TextField(
blank=True,
null=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('service-detail', args=[str(self.id)])
class Meta:
ordering = ['-dt']
verbose_name_plural = 'service calls'
I have an Equipment Detail View like this:
class EquipmentDetailView(generic.DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get_context_data(self, **kwargs):
context = super(EquipmentDetailView, self).get_context_data(**kwargs)
return context
The detail view has two buttons: edit and service. If I click edit I have a model form that allows me to edit that instance of the Equipment model successfully.
However, when I click the service button, my form comes up to create an instance of the Action model, but when I submit it tells me that the null value in name_id violates the not null constraint.
It looks like my question is, how can I pass equipment.id from the Equipment Detail view to action.name of the action create form and keep the service button concept?
Action Form:
class ServiceForm(forms.ModelForm):
class Meta:
model = Action
fields = ['incident', 'act_detail', 'result']
Action (actually service) view:
class EquipmentServiceView(generic.CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
Assuming you don't want to go with simpliest solution to include name in form fields and have urls setup as:
/equipment/<id> - equipment detail view
/service - service (or action) create view
There are several ways of passing equipment id:
1) From url
We are going to change url to accept equipment_id. That means instead of /service you will have url /equipment/<equipment_id>/service.
Probably best solution - you will use URL according to REST architecture and will have very clear structure. Client can access page from anywhere (like just copy paste link from mail) and it will work.
urls.py:
urlpatterns = [
path('equipment/<int:pk>', EquipmentDetailView.as_view(), name='equipment-detail'),
path('equipment/<int:equipment_pk>/service', EquipmentServiceView.as_view(), name='service-create')
]
Your service button should look like this: service
and finally your view:
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.kwargs['equipment_pk']
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
2) Session data
In case you want to preserve service url without adding equipment_id, you can store equipment id either in session data(on your server) or in cookies(on client). That's not exactly good - client have to go to EquipmentDetailView prior to creating Service, but this will keep your urls intact.
views.py:
class EquipmentDetailView(DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
request.session['last_equipment_pk'] = self.object.pk
return response
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.request.session.get('last_equipment_pk')
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
P.S.: name is bad field name for ForeignField - should be something like equipment or so. Those labels usually associate with CharField and expected to be strings.
Here is the code
models.py
class Submission(models.Model):
CAR = 'car'
TRUCK = 'truck'
VAN = 'van'
SUV = 'suv'
CAR_TYPES = (
(CAR, 'Car'),
(TRUCK, 'Truck'),
(VAN, 'Van'),
(SUV, 'SUV'),
)
submission_type = models.CharField(_('Submission Type'), max_length=20, choices=MEDIA_TYPES, default=CAR)
title = models.CharField(_('Title'), max_length=100, blank=False)
description = models.TextField(_('Description'))
user = models.ForeignKey(User, related_name='user_submission')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
views.py
class SubmissionCategoryList(ListView):
model = Submission
template_name = 'submission/submit_cat.html'
def get_queryset(self):
queryset = super(SubmissionCategoryList, self).get_queryset()
return queryset.filter(submission_type=self.kwargs['slug']).order_by('-date_submitted')
def get_context_data(self, **kwargs):
context = super(SubmissionCategoryList, self).get_context_data(**kwargs)
return context
urls.py
url(r'^(?P<slug>[\w-]+)/$', SubmissionCategoryList.as_view(), name='submit_category'),
The code works fine. When I go to localhost:8000/car/ It shows the list view for only the CARS submission_type, etc. But, when I type in a url that isn't a part of the choices in CAR_TYPES, for example, localhost:8000/boat/, django still shows the template for this view. My question is: How do I limit the number of choices the slug should accept? And, if it is not a part of the CAR_TYPES choices, how do I get it to ignore this view?
Have you tried validating your "slug" in views?
views.py
def get_queryset(self):
if self.kwargs['slug'] is not None and self.kways['slug'].lower() in project.settings.CAR_TYPE:
# return your queryset.filter ...
else:
# return other template or redirect to other views.
project.settings.py
CAR_TYPE = ['car', 'truck', 'van', 'suv']
or, simply just edit url regex:
(?P<slug>car|truck|van|suv)
not the best approach but definitely solve your problem.
Ok so thanks to Anzel I figured out a great solution to this problem. I'm putting this here for anyone who needs this.
I created a reference.py file that looks like this. You can call it whatever you like, I just call it reference because it's what it does.
from enum import Enum
class CarTypes(Enum):
CAR = 'car'
TRUCK = 'truck'
VAN = 'van'
SUV = 'suv'
I changed my models.py to import reference.py
I changed choices in submission_type to choices=tuple([(auto.name, auto.value) for auto in CarTypes])
from .reference import CarTypes
class Submission(models.Model):
submission_type = models.CharField(_('Submission Type'), max_length=20, choices=tuple([(auto.name, auto.value) for auto in CarTypes]), default=CarTypes.CAR)
title = models.CharField(_('Title'), max_length=100, blank=False)
description = models.TextField(_('Description'))
user = models.ForeignKey(User, related_name='user_submissions')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
And here is the final working views.py for categories
from .reference import CarTypes
class SubmissionCategoryList(ListView):
model = Submission
template_name = 'submission/submit_cat.html'
CAR_TYPES = [auto.value for auto in CarTypes]
def get_queryset(self):
if self.kwargs['slug'] in self.CAR_TYPES:
queryset = super(SubmissionCategoryList, self).get_queryset()
return queryset.filter(submission_type=self.kwargs['slug']).order_by('-date_submitted')
else:
return Http404
def get_context_data(self, **kwargs):
context = super(SubmissionCategoryList, self).get_context_data(**kwargs)
context['submission_type'] = self.kwargs['slug']
return context
You can put this same queryset in a DetailView and it should work when a user searches site.com/car/2 or something like that
I have a class Task with the following implementation:
class Task(models.Model):
author = models.ForeignKey(Author, unique=False)
name = models.CharField(max_length=255)
completed = models.BooleanField(default=False)
deadline = models.DateTimeField(null=True, blank=True)
pub_date = models.DateTimeField(auto_now_add=True, editable=False)
edit_date = models.DateTimeField(auto_now_add=True, auto_now=True, editable=False)
tag = models.ManyToManyField(Tag, related_name='tags', null=True, blank=True, default=None)
# group = models.ForeignKey(Group, blank=True, default=None)
def __str__(self):
return u'%s' % (self.name)
def toggle_complete(self):
self.completed = not self.completed
def is_past_deadline(self):
return timezone.now() > self.deadline
And I am trying to do a simple form that creates a new Task with a Title. But, as you can see, the author attribute can not be null (and don't want to, of course).
Author is implemented as follows:
class Author(models.Model):
user = models.OneToOneField(User, primary_key=True)
name = models.CharField(max_length=30)
def __str__(self):
return u'%s' % (self.user)
I tried and tried to hide the author field and, overriding methods like get_form_kwargs, form_valid, get_form to set it to the current logged user, but I always fail. Simply, the id is neither sent as post data (as seein in the debug trace), nor fetched from the view itself.
My best result has been showing the author field, creating the user correctly, but getting a "success_url" not found, even with the model having a get_absolute_url method declared.
The view I am working with is implemented like:
class HomeView(CreateView, MultipleObjectMixin):
# common
model = models.Task
template_name = 'home.html'
#form
form_class = TaskForm
# list
object_list = model.objects.all()
context_object_name = 'tasks'
paginate_by = 40
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('taskr:index'))
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(HomeView, self).get_form_kwargs()
kwargs['initial']['author_id'] = self.request.user.id
return kwargs
def form_valid(self, form):
task = form.save(commit=False)
task.user = models.Author.objects.get(user=self.request.user) # use your own profile here
task.save()
return HttpResponseRedirect(self.get_success_url())
For the record, the MultipleObjectMixing part of the view works flawlessly.
I am desperate, is there any good resource for Django forms, one like http://ccbv.co.uk/? Thanks.
After a good night sleep, while cleaning up, I tried fixing the form_valid in the CreateView descendant and I got it right.
The trick is in
task.user = models.Author.objects.get(user=self.request.user)
and it failed to me because of desperate copy-pasting. The problem was that my Task model has no user attribute, but an author. So
task.author = models.Author.objects.get(user=self.request.user)
fixes it all.
Sorry for the stupid question.