NoReverseMatch: Django URL - python

I cannot display the products in the ProductDetailView.
ProductDetailView is returning empty string to 'category-detail'..
In Product Detail View it links to:
http://127.0.0.1:8000/products/products/6/
but returns this Error:
Reverse for 'category_detail' with arguments '('',)' not found. 1 pattern(s) tried: ['products/categories/(?P<pk>\\d+)$']
The Information shows up fine in list view for both category and product.
urls.py
url(r'^categories/(?P<pk>\d+)/$', views.CategoryDetailView.as_view(), name='category_detail'),
models.py
class Category(models.Model):
"""
Model For a product category
"""
c_name = models.CharField(max_length=200, help_text="Enter a Product Category: ")
def __str__(self):
"""
String Representation for the Model object
"""
return self.c_name
def get_absolute_url(self):
"""
Return an absolute URL to access a product instance
"""
return reverse('category_detail', args=[str(self.id)])
views.py
class CategoryDetailView(generic.DetailView):
template_name = 'category_detail.html'
context_object_name = 'category_detail'
paginate_by = 2
model = Category
def get_object(self):
return get_object_or_404(self.model, pk=self.kwargs['pk'])
def get_context_data(self, *args, **kwargs):
context = super(CategoryDetailView, self).get_context_data(*args, **kwargs)
context['products'] = self.get_object().products.all()
return context
class ProductDetailView(generic.DetailView):
model = Product
context_object_name = 'product_detail'
template_name = 'product_detail.html'
paginate_by = 2
def get_object(self):
return get_object_or_404(self.model, pk=self.kwargs['pk'])
def get_context_data(self, *args, **kwargs):
context = super(ProductDetailView, self).get_context_data(*args, **kwargs)
return context
html
<p><strong>Category:</strong> {{ product.category }}</p>
What I am missing?

You need to take out the context names
class CategoryDetailView(generic.DetailView):
...
context_object_name = 'category_detail' #
...
class ProductDetailView(generic.DetailView):
...
context_object_name = 'product_detail.html'
...
These are messing with the linking system.
Another thing.. in the product_detail.html. you need to add the product.pk into the url
update
Delete
This Should work..
Also maybe link back to the product-detail view when you have finished editing the product :)

I think your CategoryDetailView need get_object.
from django.shortcuts import get_object_or_404
...
class CategoryDetailView(generic.DetailView):
template_name = 'category_detail.html'
context_object_name = 'category_detail'
paginate_by = 2
model = Category
def get_object(self):
return get_object_or_404(self.model, pk=self.kwargs['pk'])
def get_context_data(self, *args, **kwargs):
context = super(CategoryDetailView, self).get_context_data(*args, **kwargs)
context['products'] = self.get_object().products.all()
return context
and change the url;
url(r'^categories/(?P<pk>\d+)$', ...
to:
url(r'^categories/(?P<pk>\d+)/$', ...

Related

Trying to restric a queryset in Django base on foreign key relationship

I am trying to make a form in Django that gives the user a limited selection based on a foreign key. To be more exact, the form has 2 fields, a ModelChoiceField and a simple text field. This form's purpose is to allow a user to add a Riddle to a previously created Room. So the goal is to limit the ModelChoiceField to only allow the currently logged-in user to add riddles to just their own rooms.
forms.py:
from django import forms
from .models import Project, Riddle
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['title', 'max_players', 'has_actor', 'scenario']
class RiddleForm(forms.ModelForm):
project = forms.ModelChoiceField(queryset=Project.objects.all(),
empty_label=None,
widget=forms.Select(attrs={'class': 'form-control'}),
label='Project')
class Meta:
model = Riddle
fields = ['project','description']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.all()
self.fields['project'].label_from_instance = lambda obj: obj.title
models.py:
from django.db import models
from django.contrib.auth import get_user_model
from django.urls import reverse
User = get_user_model()
class Project(models.Model):
title = models.CharField(max_length=255, unique=True)
max_players = models.PositiveIntegerField(default=0)
has_actor = models.BooleanField(default=False)
scenario = models.TextField(blank=True, null=True)
#number_of_riddles = models.PositiveIntegerField(default=0)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='rooms')
def get_absolute_url(self):
return reverse(
"rooms:project_list",
kwargs={
"username": self.user.username,
#"pk": self.pk
}
)
class Riddle(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
description = models.TextField()
urls.py:
from django.urls import path
from .views import ProjectListView, ProjectDetailView, ProjectCreateView, ProjectUpdateView, ProjectDeleteView, RiddleAddView
app_name = 'rooms'
urlpatterns = [
path('projects/madeby/<slug:username>', ProjectListView.as_view(), name='project_list'),
path('projects/<slug:username>/<int:pk>/', ProjectDetailView.as_view(), name='project_detail'),
path('projects/create/', ProjectCreateView.as_view(), name='project_create'),
path('projects/update/<int:pk>', ProjectUpdateView.as_view(), name='project_update'),
path('projects/delete/<int:pk>/', ProjectDeleteView.as_view(), name='project_delete'),
path('projects/addriddle/', RiddleAddView.as_view(), name='riddle_add'),
]
views.py:
from django.urls import reverse_lazy, reverse
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import View, generic
from django.views.generic import CreateView, DetailView, UpdateView
from .models import Project, Riddle
from .forms import ProjectForm, RiddleForm
from django.contrib.auth import get_user_model
from django.http import Http404
from braces.views import SelectRelatedMixin
from django.contrib import messages
User = get_user_model()
class ProjectCreateView(CreateView):
model = Project
form_class = ProjectForm
template_name = 'rooms/project_form.html'
#success_url = reverse_lazy('rooms:project_list username=')
def get_success_url(self):
return reverse('rooms:project_list', kwargs= {'username': self.request.user})
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
#def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context['riddle_form'] = RiddleForm()
# return context
#def form_valid(self, form):
#project = form.save()
# project_id = self.kwargs.get('pk')
# project = Project.objects.get(id=project_id)
# riddle_form = RiddleForm(self.request.POST)
#if riddle_form.is_valid():
# riddle = riddle_form.save(commit=False)
# riddle.project = project
# riddle.save()
#return super().form_valid(form)
class ProjectDetailView(DetailView):
model = Project
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
project_id = self.kwargs.get('pk')
project = Project.objects.get(id=project_id)
context['project'] = project
return context
class ProjectUpdateView(UpdateView):
model = Project
form_class = ProjectForm
template_name = 'rooms/project_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['riddle_form'] = RiddleForm()
return context
#def form_valid(self, form):
#project = form.save()
# project_id = self.kwargs.get('pk')
# project = Project.objects.get(id=project_id)
# riddle_form = RiddleForm(self.request.POST)
#if riddle_form.is_valid():
# riddle = riddle_form.save(commit=False)
# riddle.project = project
# riddle.save()
#return super().form_valid(form)
#def form_valid(self, form):
# form.save()
# return redirect('rooms:project_list')
class ProjectListView(generic.ListView):
model = Project
template_name = "rooms/room_list.html"
def get_queryset(self):
try:
self.room_user = User.objects.prefetch_related('rooms').get(
username__iexact=self.kwargs.get('username')
)
except User.DoesNotExist:
raise Http404
else:
return self.room_user.rooms.all()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["room_user"] = self.room_user
return context
class ProjectDeleteView(LoginRequiredMixin, SelectRelatedMixin, generic.DeleteView):
model = Project
select_related = ('user',)
#success_url = reverse_lazy('home')
def get_success_url(self):
return reverse('rooms:project_list', kwargs= {'username': self.request.user})
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(user_id=self.request.user.id)
def delete(self, *args, **kwargs):
messages.success(self.request, "Post Deleted")
return super().delete(*args, **kwargs)
class RiddleAddView(CreateView):
model = Riddle
form_class = RiddleForm
template_name = 'rooms/riddle_form.html'
def form_valid(self, form):
project = form.cleaned_data.get('project')
project_title = project.title
try:
project = Project.objects.get(title=project_title)
except Project.DoesNotExist:
messages.error(self.request, "No project with the title '{}' was found.".format(project_title))
return super().form_invalid(form)
form.instance.project = project
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('rooms:project_detail', kwargs={'username': self.object.project.user.username, 'pk': self.object.project.id})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['riddle_form'] = RiddleForm()
#context['project_id'] = self.kwargs['project_id']
return context
For now, I just have the Project.objects.all() but that does not achieve the functionality I want.
I have tried using the select_related(), filter() methods and the attribute__foreignattribute syntax to no success. Any ideas of how I should go about this are welcome! Taking into account how common this operation is, the solution is probably something pretty obvious, but I haven't been able to come up with something to fit my case.
Thanks in advance!
I think you can pass the user as an argument for the RiddleForm:
In RiddleAddView you can define user as a parameter for the RiddleForm:
class RiddleAddView(CreateView):
...
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
...
Then you can use it to restrict the queryset in the RiddleForm:
class RiddleForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(user=user)
...
Also, you can use the reverse relationship:
class RiddleForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['project'].queryset = user.rooms.all()
...

How to use multiple models in one ListView Class on Django

I want to create pege which previews auhors profile and posts with Django.
I created UserPostListView Class, then I want to search on Profile model by author's name and get profile.
How can I do this?
All code here
models.py
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg',upload_to='profile_pics')
def __str__(self):
return f'{self.user.username} Profile'
def save(self):
super().save()
img = Image.open(self.image.path)
output_size = (300,300)
img.thumbnail(output_size)
img.save(self.image.path)
views.py(UserPostListView Class)
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html'
context_object_name = 'posts'
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
def get_context_data(self, **kwargs):
context = super(UserPostListView, self).get_context_data(**kwargs)
context['profiles'] = Profile.objects.all()
return context
If I understand, you want the author profile of the posts which are in your post queryset that is a single user, so in your get_context_data you can get author for each post.
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["profile"] = Profile.objects.filter(user__username=self.kwargs.get("username"))
return context

How to use objects specified by a manager instead of the default objects in a class based or function view?

My code:
models.py
class EmployeeManager(models.Manager):
def get_queryset(self):
return super().get_queryset().exclude(employed=False)
class NotEmployedEmployee(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(employed=False)
class Employee(models.Model):
objects = EmployeeManager()
not_employed = NotEmployedEmployees()
name = models.CharField(max_length=250)
employed = models.BooleanField(default=True, blank=True, null=True)
views.py
class EmployeeListView(ListView):
model = Employee
template_name = 'tmng/employee_list.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
resultset = EmployeeFilter(self.request.GET, queryset=self.get_queryset())
context['filter'] = resultset
return context
class EmployeeUpdateView(UpdateView):
template_name = 'tmng/employee_update.html'
model = Employee
fields = '__all__'
def form_valid(self, form):
self.name = form.cleaned_data.get('name')
return super().form_valid(form)
def get_success_url(self):
messages.success(self.request, f'Employee "{self.name}" changed!')
return '/'
For all my currently working employees my list and update view works fine.
But I also want a list/update-view for my not-employed employees so I can 'reactivate' them once they rejoin the company.
For the list view I found a semi-solution by using a function based view.
views.py
def not_employed_employee_list_view(request, *args, **kwargs):
template_path = 'tmng/employee_not_employed.html'
context = {'employees': Employee.not_employed.all()}
return render(request, template_path, context)
So what I'm looking for is a way to see list/update non employed employees. Is there a way to say to class based / functions views to use not the default employees but the 'non_employed' employees?
I did not create new templates, but just created a new class based list view
class EmployeeNotEmployedListView(EmployeeListView, ListView):
def get_queryset(self):
return Employee.not_employed.all()
And for the update view, I updated the default Employee update view
class EmployeeUpdateView(UpdateView):
template_name = 'tmng/employee_update.html'
model = Employee
fields = '__all__'
def get_queryset(self):
return Employee.objects.all() | Employee.not_employed.all()
def form_valid(self, form):
self.name = form.cleaned_data.get('name')
return super().form_valid(form)
def get_success_url(self):
messages.success(self.request, f'Employee "{self.name}" changed!')
return '/'

Why is Django not able to display the correct categories according to tags?

I have been stuck on this problem for 2 days. My product is supposed to show categories that match the tag the user has selected. However, whenever I click on a tag, it shows this error message:
Page not found (404)
No category found matching the query
Request Method: GET
Request URL: http://127.0.0.1:8000/tag/class-1
Raised by: content.views.TagDetail
The TagDetail view:
class TagDetail(DetailView):
model = Category
template_name = 'content/tag_detail.html'
context_object_name = 'categories'
def get_queryset(self):
self.tag = get_object_or_404(Tag, slug=self.kwargs['slug'])
return Category.objects.filter(tag=self.tag).order_by('id')
def get_context_data(self,**kwargs):
context = super(TagDetail, self).get_context_data(**kwargs)
self.tag = get_object_or_404(Tag, slug=self.kwargs['slug'])
context['tag'] = self.tag
return context
My Tag model:
class Tag(models.Model):
title = models.CharField(max_length=50)
slug = models.SlugField(editable=False)
def __str__(self):
return self.title
def save(self,*args,**kwargs):
self.slug = slugify(self.title)
super(Tag, self).save(*args, **kwargs)
My Category model:
class Category(models.Model):
image = models.ImageField(blank=True, null=True, default='book.jpg', upload_to='media')
title = models.CharField(max_length=150)
slug = models.SlugField(editable=False)
brief = models.CharField(max_length=150,default=None)
tag = models.ManyToManyField(Tag,related_name='categories',blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Category, self).save(*args, **kwargs)
def __str__(self):
return self.title
def category_tag(self):
return ', '.join(str(tag) for tag in self.tag.all())
Please help me out here!
A DetailView is supposed to deal with a single object. Here you want to display a list of Category which are related to a Tag instance. What is happening is that the DetailView is trying to filter the queryset you return with the slug you meant to pass for the Tag but obviously there is no such Category giving you an error. What you can do instead is simply keep it as a detail view for Tag:
class TagDetail(DetailView):
model = Tag
template_name = 'content/tag_detail.html'
context_object_name = 'tag'
Next in the template you can simply loop over the related Category instances like:
{% for category in tag.categories.all %}
{{ category.title }}
{% endfor %}
OR you can use a ListView for Category and override its get_queryset to filter based on the Tag:
from django.views.generic import ListView
class CategoryListView(ListView):
model = Category
ordering = 'id'
template_name = 'content/tag_detail.html'
context_object_name = 'categories'
def get_queryset(self):
self.tag = get_object_or_404(Tag, slug=self.kwargs['slug'])
super().get_queryset().filter(tag=self.tag)
def get_context_data(self, *args, **kwargs):
context = super.get_context_data(*args, **kwargs)
context['tag'] = self.tag
return context

How to access current user in Django class based view

I cannot access current logged in user in Django class based view:
models.py:
class Userproject(models.Model):
class Meta:
verbose_name = u'pp'
verbose_name_plural = u'pps'
user = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name="project", verbose_name=_("Владелец проекта"))
#user = models.ForeignKey(User, unique=True)
name = models.TextField(u'Название проекта', unique=True)
date_created = models.DateTimeField(u'Дата создания',
default=datetime.now(), db_index=True)
date_until = models.DateTimeField(u'Оплачен по', default=datetime.now(), db_index=True)
views.py:
#login_required
class UserprojectList(ListView):
context_object_name = 'userproject_list'
queryset = Userproject.objects.filter(user=self.request.user)
template_name = 'userproject_list.html'
when i navigate to url i see error:
name 'self' is not defined
if i change self.request.user to request.user
the error is: name 'request' is not defined
Note that without user filtering view is working and shows data
django 1.8.5
You can just overwrite get_queryset:
#login_required
class UserprojectList(ListView):
context_object_name = 'userproject_list'
template_name = 'userproject_list.html'
def get_queryset(self):
return Userproject.objects.filter(user=self.request.user)
Also you can't use decorators on classes, so you have to write something like this:
from django.utils.decorators import method_decorator
class UserprojectList(ListView):
context_object_name = 'userproject_list'
template_name = 'userproject_list.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(UserprojectList, self).dispatch(*args, **kwargs)
def get_queryset(self):
return Userproject.objects.filter(user=self.request.user)
#pythad's answer is correct. But on Django 1.9+, instead of the dispatch method, you can use django.contrib.auth.mixins.LoginRequiredMixin to replace the old-style #login_required decorator.
from django.contrib.auth.mixins import LoginRequiredMixin
class UserprojectList(LoginRequiredMixin, ListView):
context_object_name = 'userproject_list'
template_name = 'userproject_list.html'
def get_queryset(self):
return Userproject.objects.filter(user=self.request.user)
I would try to do that in the __init__ method:
#login_required
class UserprojectList(ListView):
context_object_name = 'userproject_list'
template_name = 'userproject_list.html'
def __init__(self, *args, **kwargs):
super(UserprojectList, self).__init__(*args, **kwargs)
self.queryset = Userproject.objects.filter(user=self.request.user)
I think in the class-based views you would need to override the get_queryset() method in order to have access to the self.request object attached to the instance of the view rather than do this at the class level. The Classy Class-Based Views site has more information: http://ccbv.co.uk/projects/Django/1.8/django.views.generic.list/ListView/

Categories

Resources