I am new to Django and have hit a wall with a certain part of my project and I hope someone can help.
I have two ListViews in my views.py file which I would like to work similar to published/draft posts (I'm actually using sanitised and unsanitised reports). Currently, every time I try to access the "Unsanitised" list view (unsanitised_list.html), it just directs me to the the sanitised list view (intelreport_list.html)
views.py:
class IntelReportListView(ListView):
model = IntelReport
context_object_name = 'all_logs'
def get_queryset(self):
return IntelReport.objects.filter(create_date__lte=timezone.now()).order_by('-create_date')
class UnsanitisedListView(LoginRequiredMixin, ListView):
login_url = '/login/'
redirect_field_name = 'intel_db/unsanitised_list.html'
model = IntelReport
def get_queryset(self):
return IntelReport.objects.filter(sanitised__isnull=True).order_by('-create_date')
models.py
class IntelReport(models.Model):
gpms_choices = (
***REDACTED***
)
gpms = models.CharField(max_length=20, blank=True, null=True, choices=gpms_choices)
officer = models.CharField(max_length=50)
create_date = models.DateTimeField(auto_now_add=True)
sanitised = models.BooleanField(default=False)
source_eval_choices = (
***REDACTED****
)
source_eval = models.CharField(max_length=50, blank=True, null=True, choices=source_eval_choices)
intel_eval_choices = (
***REDACTED***
)
intel_eval = models.CharField(max_length=100, blank=True, null=True, choices=intel_eval_choices)
report = models.TextField(max_length=5000, blank=True, null=True)
def sanitised_log(self):
self.sanitised = True
self.save()
def get_absolute_url(self):
return reverse('log_details', kwargs={'pk':self.pk})
def __str__(self):
return str(self.pk)
urls.py
from django.urls import path
from intel_db import views
urlpatterns = [
path('welcome/', views.AboutView.as_view(), name='about'),
path('logs/', views.IntelReportListView.as_view(), name='log_list'),
path('logs/<int:pk>/', views.IntelReportDetailView.as_view(), name='log_detail'),
path('logs/new_log/', views.IntelReportCreateView.as_view(), name='new_log'),
path('unsanitised/', views.UnsanitisedListView.as_view(), name='unsanitised'),
path('logs/<int:pk>/sanitise_log/', views.sanitsed_report, name='sanitised_report'),
]
and on my landing page (landing.html), this is the link I'm using to try and reach the unsanitised_list.html:
**<a href="{% url 'unsanitised' %}">**
I cannot figure out why it keeps redirecting me to intelreport_lists.html (the sanitised logs) rather than unsanitised_list.html (the unsanitised logs).
I hope I'm not just missing something really simple but I've been over it and tried to re-write it innumerable times and can't get it right.
I hope this is enough information and any help would be greatly appreciated.
Cheers
You just have to override template_name when you extend ListView. I mean update your IntelReportListView and UnsanitisedListViewlike this,
class IntelReportListView(ListView):
model = IntelReport
context_object_name = 'all_logs'
template_name = 'YOUR_APP_NAME/intelreport_list.html'
def get_queryset(self):
return IntelReport.objects.filter(create_date__lte=timezone.now()).order_by('-create_date')
class UnsanitisedListView(LoginRequiredMixin, ListView):
login_url = '/login/'
redirect_field_name = 'intel_db/unsanitised_list.html'
template_name = 'YOUR_APP_NAME/unsanitised_list.html'
model = IntelReport
def get_queryset(self):
return IntelReport.objects.filter(sanitised__isnull=True).order_by('-create_date')
If you are interested to find out why it was redirecting to intelreport_list.html rather than unsanitised_list.html, whenever you extend ListView it will look for MODEL_NAME_list.html by default, where MODEL_NAME is name of the model that you have used inside your list views (in lower case). Since you have used model = IntelReport inside UnsanitisedListView, it's redirecting to intelreport_lists.html
Related
I am utterly confused and mortified by CBVs, seeking for help.
So I've designed the model structure and decided the url patterns as following, but simply can't write a valid CBV to facilitate the urls:
models.py
class Category(models.Model):
'''Category for men's and women's items'''
men = models.BooleanField()
women = models.BooleanField()
name = models.CharField(max_length=100)
description = models.CharField(max_length=300, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta():
verbose_name_plural = 'Categories'
def __str__(self):
return ("Men's " + self.name) if self.men else ("Women's " + self.name)
class SubCategory(models.Model):
'''Sub-category for the categories (not mandatory)'''
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
description = models.CharField(max_length=300, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta():
verbose_name = 'Sub-category'
verbose_name_plural = 'Sub-categories'
def __str__(self):
return ("Men's " + self.name) if self.category.men else ("Women's " + self.name)
class Item(models.Model):
'''Each item represents a product'''
category = models.ForeignKey(Category, on_delete=models.CASCADE)
subcategory = models.ForeignKey(
SubCategory, on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
price = models.IntegerField(default='0')
discount = models.IntegerField(null=True, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta:
ordering = ['-uploaded_date']
def __str__(self):
return self.name
def discounted_price(self):
'''to calculate the price after discount'''
return int(self.price * (100 - self.discount) * 0.01)
class ItemImage(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
image = models.ImageField(upload_to='itemimages', null=True, blank=True)
urls.py
app_name = 'boutique'
urlpatterns = [
# show index page
path('', views.IndexView.as_view(), name='index'),
# show categories of products for men or women
path('<slug:gender>/', views.ItemListView.as_view(), name='show-all'),
# show a specific category for men or women
path('<slug:gender>/cat_<int:category_pk>/', views.ItemListView.as_view(), name='category'),
# show a specific subcategory under a specific category for men or women
path('<slug:gender>/cat_<int:category_pk>/subcat_<int:subcategory_pk>/', views.ItemListView.as_view(), name='subcategory'),
# show a specific item
path('item_<int:item_pk>/', views.ItemDetailView.as_view(), name='item'),
]
views.py
class IndexView(ListView):
'''landing page'''
model = Category
template_name = 'boutique/index.html'
context_object_name = 'categories'
class ItemListView(ListView):
'''display a list of items'''
# model = Category ??? what's the point of declaring model when get_context_data() ???
template_name = 'boutique/items.html'
context_object_name = 'categories'
paginate_by = 12
def get_object(self):
obj = get_object_or_404(Category, pk=self.kwargs.get('category_pk'))
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all() # for rendering nav bar data
return context
class ItemDetailView(DetailView):
'''display an individual item'''
# model = Item
template_name = 'boutique/item.html'
context_object_name = 'item'
def get_object(self):
return get_object_or_404(Item, pk=self.kwargs['item_pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
items.html
<a href="{% url 'boutique:show-all' 'women' %}> link to categories of product for women </a>
<a href="{% url 'boutique:category' 'women' category.pk %}> link to a cat for women </a>
<a href="{% url 'boutique:subcategory' 'women' category.pk subcategory.pk %}> link to a subcat under a specific cat for women </a>
Essentially, as you can see, I'd like the ItemListView to render multiple url paths depending on what value passed into the CBV... I can do it (pass multiple values) in a FBV, however, utterly confused by the mechanism of the CBVs...
So if anyone could write an example ItemListView and according anchor url template tags (if mine are incorrect), it would be tremendulent!!! Thanks!!!
EDIT 1
class ItemListView(ListView):
'''display a list of items'''
model = Item
template_name = 'boutique/items.html'
# paginate_by = 12
def get_queryset(self):
# get original queryset: Item.objects.all()
qs = super().get_queryset()
# filter items: men/women
if self.kwargs['gender'] == 'women':
qs = qs.filter(category__women=True)
elif self.kwargs['gender'] == 'men':
qs = qs.filter(category__men=True)
if self.kwargs.get('category_pk'):
qs = qs.filter(category=self.kwargs.get('category_pk'))
if self.kwargs.get('subcategory_pk'):
qs = qs.filter(subcategory=self.kwargs.get('subcategory_pk'))
# print(qs)
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# add categories for navbar link texts
context['categories'] = Category.objects.all()
if self.kwargs.get('gender') == 'women':
context['category_shown'] = Category.objects.filter(women=True)
if self.kwargs.get('gender') == 'men':
context['category_shown'] = Category.objects.filter(men=True)
if self.kwargs.get('category_pk'):
context['category_shown']=get_object_or_404(Category, pk=self.kwargs.get('category_pk'))
if self.kwargs.get('subcategory_pk'):
context['subcategory_shown']=get_object_or_404(SubCategory, pk=self.kwargs.get('subcategory_pk'))
# print(context)
return context
After FiddleStix' answer (I haven't gone thu bluegrounds thread yet), I tried and managed to make everything work except ItemDetailView.
The urls are working fine and the filtering of get_queryset function is working fine, however,
Question 1: I wonder this might not be DRY enough?! Nevertheless, it's working. so thanks!! But could it be dryer??
Question 2: when ItemDetailView runs, the urls appear to be correct, however, the page redirect to a page rendering all items from all categories...
class ItemDetailView(DetailView):
'''display an individual item'''
# model = Item
template_name = 'boutique/item.html'
def get_object(self):
print(get_object_or_404(Item, pk=self.kwargs.get('item_pk')))
return get_object_or_404(Item, pk=self.kwargs.get('item_pk'))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# add categories for navbar link texts
# context['categories'] = Category.objects.all()
print(context)
return context
Neither is the object nor the context being printed out... and it doesn't prompt any error, either. I must have made a silly mistake somewhere!!
Answer to Question 2:
view.py
class ItemDetailView(DetailView):
'''display an individual item'''
model = Item
template_name = 'boutique/item.html'
The DetailView class should be simple if no customisation is needed, the problem is in the urlpatterns:
url.py
urlpatterns = [
path('item_<int:pk>', view.ItemDetailView.as_view(), name='item'),
]
Always use <pk> as the value passed in DetailView as it's the default. I used item_<int:item_pk> as the path url. That's why I had to use get_object() to manually get the item object (to override the default get_object()). As #bluegrounds answer suggests that the reason Class-based Views work well is they save time, for the default functions they possess.
If you wish to use item_<int:item_pk>, CBV offers the flexibility as well: simply override pk_url_kwargs = 'item_pk' in the View class - Feel free to check my other question: DetailView - get_object function confusion for clarification. #neverwalkaloner 's answer is very straightforward.
So first things first, CBVs...
They work by having "default" behaviours built into each one of them. For the ListView class, take this example view:
class ArticleListView(ListView):
model = Article
along with an example urlpattern:
path('all-articles/', ArticleListView.as_view())
and that's it. This is all that is essential for a ListView to work. This ArticleListView view would look for a template called article_list.html and in it you can use the context variable object_list to access all the Article objects that the class gets for you without you having to write the QuerySet explicitly.
Of course you can change these values, and customize the QuerySet, and do all kinds of things, but for that you'll have to study the docs. I personally find ccbv much easier to read than the docs. So for example you can see in ccbv's page about ListViews that the context_object_name = None which defaults to object_list, as mentioned above. You can change that to, for example context_object_name = 'my_articles'. You could also set the template_name = 'my_articles.html' and that will override the default template name pattern of <model>_list.html.
Now, about your code,
If you're sure that you want your URL structure to stay like it is, you could have your class view as follows to get the functionality you need:
class ItemListView(ListView):
template_name = 'boutique/items.html'
context_object_name = 'categories'
paginate_by = 12
def get_queryset(self):
# This method should return a queryset that represents the items to be listed in the view.
# I think you intend on listing categories in your view, in which case consider changing the view's name to CategoryListView. Just sayin'...
# An instance of this view has a dictionary called `kwargs` that has the url parameters, so you can do the following:
# You need some null assertions here because of the way you've setup your URLs
qs = Categories.objects.filter(men=self.kwargs['gender'], pk=self.kwargs['category_pk'])
return qs
As you can see, we didn't set many things in this class view for it to work. Namely, we didn't set the model variable as we did previously. That's because we wouldn't need it. The part that uses that variable was in the default get_queryset() method and we've overridden that method. See CCBV for more info on the default implementation of get_queryset().
Now the template will be supplied with the objects from get_queryset(), under the name categories, because that's what we set context_object_name's value to be.
NOTE: The variable model is used in other places other than get_queryset() such as the default template_name. The default template name is derived from the model name and the template_name_suffix. So if you don't set the model variable, make sure to set the template_name manually.
I'm not sure about your application's logic but I think you should change the Category model to have only one Boolean field that denotes gender. For example men if it is True and women if it's False. That way a category can't be for both men and women at the same time (unless that is something you need), and it also can't be for neither, because currently you can have a category be false for the both gender fields, which doesn't really make sense.
I would actually suggest a completely different solution that involves CHOICES, as such:
gender = models.IntegerField(null=False, CHOICES=[(1,'Men'), (2,'Women'), (3,'Other'), (4,'Unisex')], default=3)
This would store a number in the database that denotes the gender, and in your app you'll only see the correlating string (gender) to that number.
I have not tried this code on my machine so I might have missed a few things, but I hope I clarified the overall workings of CBVs.
To answer you main question, your ItemList should set model=models.Item, as alluded to in the error message, because it is meant to be a list of items.
I would set up your urls.py so that /items/ or /item_list/ goes to ItemListView.as_view(). If you then want to filter your list of items, I would not do it by changing the URL to /items/women/. Instead, I would use a URL query string like /items/?gender=women. How to do that is explained here but basically:
class ItemListView(ListView):
model = Item
template_name = "item_list.html"
paginate_by = 100
def get_queryset(self):
filter_val = self.request.GET.get('gender', 'some_default')
queryset = Item.objects.filter(
... # You'll have to work this bit out
)
return queryset
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['gender'] = self.request.GET.get('gender', 'some_default')
return context
I am building a photo album app. Where users can log in and create categories and add photo posts in those categories.
When post link is clicked it should open the post with the use of DetailView instead, it just loops back to user's post history page with post links
I have been trying to use various methods to filter queryset and return the data but I don't where I am going wrong.
posts/models.py:
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts',on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now=True)
post_title = models.CharField( max_length=30)
post_message = models.TextField()
postimage = models.ImageField(upload_to='postimgs/',verbose_name=('post picture'))
category = models.ForeignKey(Category, related_name='post_category', on_delete=models.CASCADE)
def __str__(self):
return self.post_title
def save(self,*args,**kwargs):
self.post_message_html = misaka.html(self.post_message)
super().save(*args,**kwargs)
def get_absolute_url(self):
return reverse('posts:single', kwargs={"username":self.user.username,"pk":self.pk})
class Meta:
ordering = ['-created_at']
unique_together = ['user','post_title']
posts/views.py:
class PostDetail(SelectRelatedMixin, generic.DetailView):
model =models.Post
select_related = ('user', 'category')
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(user__username__iexact=self.kwargs.get('username'))
posts/urls.py:
app_name = 'posts'
urlpatterns = [
url(r'^$',views.PostList.as_view(),name='all'),
url(r'new/$',views.CreatePost.as_view(),name='create'),
url(r'by/(?P<username>[-\w]+)',views.UserPosts.as_view(),name='for_user'),
url(r'by/(?P<username>[-\w]+)/(?P<pk>\d+)/$',views.PostDetail.as_view(),name='single'),
url(r'delete/(?P<pk>\d+)/$',views.DeletePost.as_view(),name='delete'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
HTML:
#{{post.user.username}}
</span>
<time class="time">
<a href="{% url 'posts:single' username=post.user.username pk=post.pk %}">
{{post.created_at}}
</a>
After clicking on the {{post.created_at}} should open the post detail view with details, but it is looping in post list page
your queryset is missing a comma(',')
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(user__username__iexact=self.kwargs.get('username'),category__categoryname__iexact=self.kwargs.get('categorynamee'))
and change your urls and add ^ infront
url(r'^new/$',views.CreatePost.as_view(),name='create'),
url(r'^by/(?P<username>[-\w]+)',views.UserPosts.as_view(),name='for_user'),
url(r'^by/(?P<username>[-\w]+)/(?P<pk>\d+)/$',views.PostDetail.as_view(),name='single'),
url(r'^delete/(?P<pk>\d+)/$',views.DeletePost.as_view(),name='delete'),
I want to know how to make category page view using class based view I know how to make this in function based view by using get_object_or_404(category, slug=None) But I am confused how to do this it in class based views. I tried to google this but I am unable to find anything related to this in class view.
I know I could have used function based view but I have used class based view in whole project so I thought to use them here as well
my code
models.py
from django.db import models
from django.utils import timezone
from slugger import AutoSlugField
from django.contrib.auth.models import User
from django.urls import reverse
# Create your models here.
def upload_location(instance, filename):
return "%s/%s" %(instance.slug, filename)
class Category(models.Model):
title = models.CharField(max_length= 60)
slug = AutoSlugField(populate_from='title')
parent = models.ForeignKey('self',blank=True, null=True ,related_name='children',on_delete=models.CASCADE)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
class Meta:
verbose_name_plural = 'categories'
def __unicode__(self):
return self.title
def __str__(self):
return self.title
def get_absolute_url(self, slug=None):
return reverse("posts-detail", kwargs={"slug": self.slug})
class Post(models.Model):
title = models.CharField(max_length=120)
slug = AutoSlugField(populate_from='title')
image = models.ImageField(
upload_to=upload_location,
null=True,
blank=True,
)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='postcategory')
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
ordering = ['-date_posted']
def __str__(self):
return self.title
def get_absolute_url(self, slug=None):
return reverse("posts-detail", kwargs={"slug": self.slug})
urls.py
from django.urls import path
from django.urls import path, include
from .views import PostView, PostDetailView,LatestPostView, CategoryPostListView
urlpatterns = [
path('', PostView.as_view(), name='posts-home'),
path('latest/', LatestPostView.as_view(), name='posts-latest'),
path('<slug>', PostDetailView.as_view(), name='posts-detail'),
path('category/<slug>', CategoryPostListView.as_view(), name='category-detail'),
]
views.py
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.shortcuts import redirect, render,get_object_or_404
#class based view
from django.views.generic import ListView, DetailView
from .models import Post, Category
class PostView(ListView):
template_name = 'posts/home.html'
model = Category
context_object_name = 'all_categs'
def get_queryset(self):
if self.request.user.is_authenticated:
return Category.objects.all()
else:
return Category.objects.all().exclude(title__iexact = 'Featured')[:6]
def get_context_data(self):
if not self.request.user.is_authenticated:
fcategory = Category.objects.get(title__iexact = 'Featured')
context = super(PostView, self).get_context_data()
context['latest_posts'] = Post.objects.exclude(category= fcategory).order_by('-date_posted')[0:6]
context['featured_posts'] = Post.objects.all().filter(category= fcategory).order_by('-date_posted')[0:6]
return context
else:
fcategory = Category.objects.get(title__iexact = 'Featured')
context = super(PostView, self).get_context_data()
context['latest_posts'] = Post.objects.order_by('-date_posted')
context['featured_posts'] = Post.objects.all().filter(category= fcategory).order_by('-date_posted')[0:6]
return context
# def get_success_url(self):
# return reverse('home') #add your path
class LatestPostView(LoginRequiredMixin, ListView):
template_name = 'posts/post_latest.html'
model = Post
context_object_name = 'Posts'
ordering = ['-date_posted']
paginate_by = 6
class PostDetailView(LoginRequiredMixin,DetailView):
model = Post
template_name = 'posts/post_detail.html'
class CategoryPostListView(LoginRequiredMixin, ListView):
model = Category
template_name = 'posts/category_detail.html'
# def get_queryset(self):
# category = get_object_or_404(Category, )
I thought of defining get_queryset inside CategoryPostListView. But I am not sure if it will work or not.
Firstly, if you are using ListView and want to display a list of posts, then you need model = Post.
Next, you can call get_object_or_404 in the get_queryset method. You can access slug from the URL with `self.kwargs['slug'].
Finally, you can filter the queryset to only return posts in that category.
class CategoryPostListView(LoginRequiredMixin, ListView):
model = Post
template_name = 'posts/category_detail.html'
def get_queryset(self):
category = get_object_or_404(Category, slug=self.kwargs['slug'])
return super(CategoryPostListView, self).get_queryset().filter(category=category)
Note that your problem is very similar to the dynamic filtering section in the docs.
Yes. you can use get_object_or_404 in class-based views. just add this to your CategoryPostListView:
class CategoryPostListView(LoginRequiredMixin, ListView):
model = Post
template_name = 'posts/category_detail.html'
def get_queryset(self):
category = get_object_or_404(Category, slug=self.kwargs['slug'])
# do another stuffs here
return Post.objects.filter(category=category)
for more information you can read dynamic filtering in class-based views in django official site
I want use category_slug or id_slug for detailview as productdetailview
and show all of product that category there but i don't know how do that
i used this views but i get errors 404. why is different between productdetailview and categorydetailview? could plz help me to solve this problem?
models.py
class Category(models.Model):
name = models.CharField(max_length=120,unique=True)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name
class Brand(models.Model):
name = models.CharField(max_length=120,unique=True)
class Product(models.Model):
category = models.ManyToManyField(Category,blank=True)
brand = models.ForeignKey(Brand,on_delete=models.CASCADE)
car = models.ForeignKey(Car,on_delete=models.CASCADE)
title = models.CharField(max_length=120,unique=True)
slug = models.SlugField(unique=True)
description = models.TextField(max_length=10000)
price = models.DecimalField(max_digits=10,decimal_places=2)
stock = models.PositiveIntegerField()
active = models.BooleanField(default=True)
timestamp = models.DateTimeField(auto_now_add=True,auto_now=False)
updated = models.DateTimeField(auto_now=True,auto_now_add=False)
defalut = models.ForeignKey(Category,related_name="defalut_category",blank=True,null=True,on_delete=models.CASCADE)
class Meta:
ordering = ['-timestamp']
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('products:product_detail',kwargs={"slug":self.slug})
views.py
def ProductDetail(request,slug):
product = get_object_or_404(Product,slug=slug,active=True)
context = {
"product":product
}
template_name = "product_detail.html"
return render (request,template_name,context)
def CategoryDetail(request,category_slug):
category = get_object_or_404(Category,slug = category_slug)
product = Product.objects.filter(category=category)
context = {
'category':category,
'product': product
}
template_name ="category_detail.html"
return render(request,template_name,context)
urls.py
app_name ='category'
urlpatterns = [
path('category-list/',views.CategoryList,name="category_list" ),
re_path(r'^(?P<category_slug>[-\w]+)/$', views.CategoryDetail, name='category_detail'),
My error
Page not found (404)
Request Method: GET
Request URL: http://127.0.0.1:8000/category/elctroinc/
Raised by: products.views.CategoryDetail
No Category matches the given query.
You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.
Problem was in category_slug as two friends said that #Manjo Jadhav ,#JPG
i used id instead of category_slug and it's working .
tnx for help friends.
In a ListView I render a table using the Paginator with paginate_by = 5.
In each row I have a Button that opens the UpdateView.
After a successful update I'm back on my ListView but always on the first page.
How can I change the success_url so that I'm back on the page number, from where I opened the UpdateView?
Is there a proper way in Django to solve this?
#views.py
class Orders_list(ListView):
model = Order
context_object_name = "orders"
queryset = Order.objects.all()
paginate_by = 5
template_name = "orders/orders_list.html"
#http://127.0.0.1:8000/orders/?page=4
class Orders_update(UpdateView):
model = Order
form_class = Order_UpdateForm
template_name = "orders/orders_update.html"
#success_url = '../../../orders/'
success_url = reverse_lazy('orders')
#urls.py
urlpatterns = [
url(r'^orders/$', Orders_list.as_view()),
url(r'^orders/detail/(?P<pk>\d+)/$', Orders_detail.as_view()),
]
#forms.py
class Order_UpdateForm(forms.ModelForm):
class Meta:
model = Order
fields = ['order_text', 'customer', 'cyclist']
def __init__(self, *args, **kwargs):
super(Order_UpdateForm, self).__init__(*args, **kwargs)
self.fields['cyclist'].queryset = Cyclist.objects.filter(active=True)
#models.py
class Order(models.Model):
customer = models.ForeignKey('Customer')
cyclist = models.ForeignKey('Cyclist')
order_text = models.CharField(max_length=200)
pick_up = models.CharField(max_length=200)
destination = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
changed_date = models.DateTimeField(blank=True, null=True)
def created(self):
self.changed_date = timezone.now()
self.save()
def __str__(self):
return self.order_text
class Customer(models.Model):
company_name = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
changed_date = models.DateTimeField(blank=True, null=True)
def created(self):
self.changed_date = timezone.now()
self.save()
def __str__(self): return self.company_name
class Cyclist(models.Model):
lastname = models.CharField(max_length=20)
firstname = models.CharField(max_length=20)
created_date = models.DateTimeField(default=timezone.now)
changed_date = models.DateTimeField(blank=True, null=True)
active = models.BooleanField(default=True)
def created(self):
self.changed_date = timezone.now()
self.save()
def __str__(self):
return self.lastname
Update
I found a way to solve this (may be). But I still miss something.
What I did so far:
In the ListView template I extended the Url...
window.open("{{ request.path }}update/" + id + "/?page=" + {{ page_obj.number }});
...then in the view I overwrote the get_context_data method...
#views.py
class Orders_update(UpdateView):
model = Order
form_class = Order_UpdateForm
template_name = "orders/orders_update.html"
page = "1" # Assigned
def get_context_data(self, **kwargs):
context = super(Orders_update, self).get_context_data(**kwargs)
page = self.request.GET.get('page') # Modified
return context
success_url = '../../../orders/?page=' + str(page) # Should be used here to redirect to a specific page on a ListView
...but now...how can I access the variable page in views.py in the success_url?
I think this is something very basic (hopefully) and I think I don't understand how scope works in Python.
This is what finally worked for me. Basically I just overwrote the get_success_url to have access to the page parameter from the request.
class Orders_update(UpdateView):
model = Order
form_class = Order_UpdateForm
template_name = "orders/orders_update.html"
def get_success_url(self):
global page
page = self.request.GET.get('page')
return reverse_lazy('orders')
I just got finished pulling my hair out with this same problem. Here is what I did. Start in views.py in your ListView, Order_list, and add context data:
def get_context_data(self, **kwargs):
kwargs['current_page'] = self.request.GET.get('page', 1)
return super().get_context_data(**kwargs)
The above code passes only the current page number into the ListView template.
Then go to the corresponding list view template and add the following in the object list loop:
{% if is_paginated %}
<a href="{% url 'order_update' pk=order.pk %}?next={{current_page}}">Update
</a>
{% else %}
<a href="{% url 'order_update' pk=order.pk %}">Update
{% endif %}
The if statement allows the page to work without pagination. Note the ?next={{current_page}} variable. The "current_page" comes from the context_data in the view. This bit passes the current page number to the "next" variable in the url of the page in the anchor tag. This is what my update view url looks like with this variable in it: http://localhost:8000/food/406/update?next=1. See the ?next=1 in the url.
Next, go back to views.py to your UpdateView, Order_update, and change the success url with this code:
def get_success_url(self, **kwargs):
if self.request.GET.get('next'):
return f"/orders?page={self.request.GET.get('next', 1)}"
else:
return '/orders'
You'll have to remove the success_url declaration in this view. The def get_success_url function wants a string. Reverse seems to work well, but a formatted string will not work in a redirect() statement. It needs to be a plain string. See TypeError: quote_from_bytes() expected bytes after redirect for more on that.
If I broke that down too much, I'm sorry. I don't mean to insult your knowledge. I'm still learning and I wrote this the way I would have needed it earlier today before I figured this out.