getting object with slug or pk in querysets - regex

In django doc says that
>>> entry = Entry.objects.get(id=10)
is faster than this:
>>> entry = Entry.object.get(headline="News Item Title")
So I changed my view from this:
def myview(request, slug):
post = get_object_or_404(Post, slug=slug)
#...
to this:
def myview(request, id, slug):
post = get_object_or_404(Post, pk=pk)
#...
But my slug is not used in my view. In that case both urls below get the correct post because of valid regex:
127.0.0.1:8000/posts/5/my-first-post/
127.0.0.1:8000/posts/5/mylblablabalg/
And I dont want this. Then I canged my view to this:
def myview(request, id, slug):
post = get_object_or_404(Post, pk=pk)
if post.slug == slug:
return render(request, 'blog/index.html', {'post': post})
else:
return HttpResponseRedirect(reverse('blog:index', args=(id, post.slug)))
It does what I want which redirects the same page with the corrected slug but I doubt if it is ok in terms of performanse. What about db_index? Should I use it in SlugField?

If you are going to pull posts from the database via the SlugField, then yes, you should tell the database to index that field for performance increase. You should also set it to unique.

Related

No Post matches the given query. django blog

everything is ok in the model but when I try to retrieve the object the error "No Post matches the given query." is shown.
I don't know what is the problem that shows this error?
**Model**
class Post(CoreField):
....
slug = models.SlugField(max_length=250, unique_for_date='publish')
tags = TaggableManager()
publish = models.DateTimeField(default=timezone.now)
def get_absolute_url(self):
return reverse('news:post_detail',
args=[self.publish.year,
self.publish.month,
self.publish.day,
self.slug])
**url**
path('<int:year>/<int:month>/<int:day>/<slug:post>/', views.post_detail, name='post_detail'),
**views**
def post_detail(request, year, month, day, post):
single_post = get_object_or_404(Post,
publish__year=year,
publish__month=month,
publish__day=day,
slug=post
)
# List of active comments for this post
comments = post.comments.filter(active=True)
# Form for users to comment
form = CommentForm()
return render(request, 'news/post_detail.html', {'post': single_post, 'comments': comments, 'form': form})
when I remove the remove the "int:year/int:month/int:day/" form url it works.
but when I pass the "int:year/int:month/int:day/slug:post/" it does't work.
What is the proble and where it happens????
In your post_detail view, I think you should pass "slug" as an argument to the view instead of "post" because post is not a model field nor is it defined anywhere. And the "slug=post" kwarg should probably be reversed to avoid further errors.
So the post_detail view should probably look like this:
def post_detail(request, year, month, day, slug):
single_post = get_object_or_404(Post,
publish__year=year,
publish__month=month,
publish__day=day,
post=slug
)

Passing arguments to Django CBV for queryset

I'm trying to figure out how can I pass arguments from a generic view where a user submits a form to a cbv template view.
My first generic view is as such:
def homepage(request):
if request.method == 'POST':
form = SearchForm(request.POST)
if form.is_valid():
form_data = form.cleaned_data
return redirect('filtered', city=form_data['city'], category=form_data['category'])
else:
form = SearchForm()
return render(request, 'index.html', context={'form': form})
What Im trying to achieve is that when a user choses a city and a category then he gets submitted to a homepage that is a TemplateView where there's a queryset based on objects that are in the selected city and of selected category.
class ListAd(ListView):
model = Ad
template_name = 'filtered'
queryset = # here Im expecting to make a query based on the 'city' and 'category' chosen in previous view
How to pass the city and category arguments to the ListAd view so I can use them in the queryset? This is the ursl.py
urlpatterns = [
path('list_new/<city>/<category>/', views.ListAd.as_view(), name='filtered'),
]
Any help would be appreciated. Thank you
I think that this should works.
class ListAd(ListView):
model = Ad
template_name = 'filtered'
queryset = Ad.objects.all()
def get_queryset(self):
queryset = super(ListAd, self).get_queryset()
queryset = queryset.filter(city=self.kwargs["city"], category=self.kwargs["category"])
return queryset

Handling Redirect with URL that has multiple PK

I'm new to Django and having trouble redirecting after the AddContactEvent form has been filled out. After submitting the form, here is the redirect error:
No URL to redirect to. Either provide a url or define a
get_absolute_url method on the Model.
I am having trouble figuring out how to redirect it since the AddContactEvent url path('contacts/<int:pk1>/addcontactevent)
only has one pk. In the EventDetail url there are clearly two pk which would have the contact pk and the event pk. The EventDetail page seems to be creating, but I can't get it to redirect to that page due to multiple PK. how would you handle the redirect?
urls.py
path('contacts/<int:pk>', contact_detail.as_view(), name="contact_detail"),
path('contacts/<int:pk1>/addcontactevent', AddContactEvent.as_view(), name="addcontactevent"),
path('contacts/<int:pk1>/event/<int:pk2>/update', UpdateContactEvent.as_view(), name="updatecontactevent"),
path('contacts/<int:pk1>/event/<int:pk2>', EventDetail.as_view(), name="eventdetail"),
views.py
class AddContactEvent(CreateView):
form_class = ContactEventForm
template_name = 'crm/contactevent.html'
def dispatch(self, request, *args, **kwargs):
"""
Overridden so we can make sure the `Ipsum` instance exists
before going any further.
"""
self.contact = get_object_or_404(Contact, pk=kwargs['pk1'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
""" Save the form instance. """
contact = get_object_or_404(Contact, pk=self.kwargs['pk1'])
form.instance.contact = contact
form.instance.created_by = self.request.user
return super().form_valid(form)
class UpdateContactEvent(UpdateView):
model = Event
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
contact = get_object_or_404(Contact, pk=pk1)
event = get_object_or_404(Event, pk=pk2)
return event
class DeleteContactEvent(DeleteView):
model = Event
class EventDetail(DetailView):
template_name = 'crm/eventdetail.html'
model = Event
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
contact = get_object_or_404(Contact, pk=pk1)
event = get_object_or_404(Event, pk=pk2)
return event
one way to get rid of the error is to define a get absolute url in contact model
def get_absolute_url(self):
return reverse("contact_detail", kwargs={"pk": self.pk})
You have a saved Event object (which has a pk) and you have the contact pk
def get_success_url(self):
return reverse('eventdetail', kwargs={'pk1': self.kwargs['pk1'], 'pk2': self.object.pk})

Check if user voted and if not save vote

In a simple survey app users should vote only once on a set of questions (survey). I'm able to check if the user voted (either lazy user or authorized user), but only if I prepopulate the fields with test data in the database. I'm also aware that a lazy user could just delete the cookie and would be able to vote again.
I'm not sure how to call save() after form.is_valid(), because I can't call it twice.
models.py
class Vote(models.Model):
user = models.ForeignKey(User)
survey = models.ForeignKey(Survey)
vote = models.BooleanField(default=False) # not in use yet
def __unicode__(self):
return self.vote
views.py
#allow_lazy_user
def survey_detail(request, slug, template_name='survey.html'):
# allows only active surveys to be called otherwise displays HTTP 404
survey = get_object_or_404(Survey, is_active=True, slug=slug)
# checks for user id's in current survey
voter = [
v.user.id for v in Vote.objects.filter(survey=survey)
]
# checks if user already voted on survey
if request.user.id in voter:
# TODO: use different template or redirect
return render(request, 'index.html')
form = ResponseForm(request.POST or None, survey=survey)
if form.is_valid():
response = form.save(commit=False)
# gets the current user (lazy or not)
response.user = request.user
response.save()
# HOW DO I SAVE VOTER HERE?
return HttpResponseRedirect(reverse('survey:confirm', args=(slug,)))
data = {
'response_form': form,
'survey': survey
}
return render(request, template_name, data)
Another thought I have is to use the boolean field vote in the model and check for True or False in the template.
Okay, I solved it with
#allow_lazy_user
def survey_detail(request, slug, template_name='survey.html'):
[...]
form = ResponseForm(request.POST or None, survey=survey)
if form.is_valid():
response = form.save(commit=False)
response.user = request.user
response.save()
if not request.user.id in voter:
vote = Vote(user=request.user, survey=survey, vote=True)
vote.save()
return HttpResponseRedirect(reverse('survey:confirm', args=(slug,)))
[...]
I'm sure there are better ways, but it works for now.
New solution, since survey_id and user_id are already in model Response:
#allow_lazy_user
def survey_detail(request, slug, template_name='itbarometer/survey_detail.html'):
# allows only active surveys to be called otherwise displays HTTP 404
survey = get_object_or_404(Survey, is_active=True, slug=slug)
categories = [c.name for c in Category.objects.filter(survey=survey)]
voter = [v.user.id for v in Response.objects.filter(survey=survey)]
# checks if survey_id and user_id are already linked
# if yes, redirect
if request.user.id in voter:
return redirect('bereits-abgeschlossen/')
form = ResponseForm(request.POST or None, survey=survey)
if form.is_valid():
response = form.save(commit=False)
# gets the current user (lazy or not)
response.user = request.user
response.save()
return HttpResponseRedirect(reverse('survey:confirm', args=(slug,)))
data = {
'response_form': form,
'survey': survey,
'categories': categories
}
return render(request, template_name, data)

using filters with django-endless-pagination

I'm using Django endles-pagination to load the pages in infinite scroll. I also have some filters that filter the data according to the criteria (for eg, price slider filtering according to price). Now when the page loads, the filter right now filters only from the page loaded, though I want it to filter it from all the pages that have been or are to be loaded. Is there a way to do this (by making some ajax request or something)?
Any help on this would be great. Thanks a lot.
To filter the data you have to redefine get_queryset() method in the views requesting the filtered query.
For example I request the current language in template to filter the Blog posts based on the language:
class Blog(AjaxListView):
context_object_name = "posts"
template_name = 'cgapp/blog.html'
page_template = 'cgapp/post_list.html'
def get_queryset(self):
if self.request.LANGUAGE_CODE == 'en': #request value of the current language
return News.objects.filter(language='en') #return filtered object if the current language is English
else:
return News.objects.filter(language='uk')
To filter the queryset based on the users input, you may refer to POST method:
from app.forms import BlogFilterForm
class Blog(LoginRequiredMixin, AjaxListView):
context_object_name = "posts"
template_name = 'blog/blog.html'
page_template = 'blog/post_list.html'
success_url = '/blog'
def get_queryset(self): # define queryset
queryset = Post.objects.all() # default queryset
if self.request.method == 'POST': # check if the request method is POST
form = BlogFilterForm(self.request.POST) # define form
if form.is_valid():
name = form.cleaned_data['name'] # retrieve data from the form
if name:
queryset = queryset.filter(name=name) # filter queryset
else:
queryset = queryset
return queryset
def get_context_data(self, **kwargs):
context = super(Blog, self).get_context_data(**kwargs)
context['form'] = BlogFilterForm() # define context to render the form on GET method
return context
def post(self, request, *args, **kwargs): # define post method
return super(Blog, self).get(request, args, kwargs)
The endless pagination should work fine.