I've started using Django 2.0 and Python 3.6.3 to develop a website which will display customized "Posts", as in a blog Post.
I want the user to be able to click a button that basically says "Random Post". This button will take them to a template which loads a random blog post.
Here's my Post model:
class Post(models.Model):
post_id = models.AutoField(primary_key=True)
... other fields
... other fields
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
Here are some relevant views:
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
Here is my problematic view:
import random
def random_post(request):
post_ids = Post.objects.all().values_list('post_id', flat=True)
random_obj = Post.objects.get(post_id=random.choice(post_ids))
context = {'random_post': random_obj,}
return render(request, 'blog/random_post.html', context)
Here I am trying to create a Values List of all post_id values for the Post model. Then I'm trying to get a random choice from this Values List, which would be a random id. Then I'm trying to create a context and render a template using this logic.
Here are relevant urlpatterns:
urlpatterns = [
path('post/<int:pk>/',
views.PostDetailView.as_view(),name='post_detail'),
path('post/random/<int:pk>', views.random_post, name='random_post'),
Needless to say this is not working.
If I leave off the "int:pk" it renders a blank template with no data - no blog Post. If I include the , it causes an error - No Arguments Found. I assume that no data is being queried in the view, or the data isn't being properly sent from the view to the template.
I am new to Django. I appreciate your help!
For the behaviour you want, your URLs should be:
urlpatterns = [
path('post/random/', views.random_post, name='random_post'),
path('post/<int:pk>/',
views.PostDetailView.as_view(),name='post_detail'),
]
and your random_post view is:
def random_post(request):
post_count = Post.objects.all().count()
random_val = random.randint(1, post_count-1)
post_id = Post.objects.values_list('post_id', flat=True)[random_val]
return redirect('post_detail', pk=post_id)
This makes two queries - one to get the count of all posts, and one to get ID of the post in the random position. The reason for doing this is that then you don't have to get all of the IDs from the database - if you have thousands of posts, that would be very inefficient.
Here's the view that works.
def random_post(request):
post_count = Post.objects.all().count()
random_val = random.randint(0, post_count-1)
post_id = Post.objects.values_list('post_id', flat=True)[random_val]
return redirect('post_detail', pk=post_id) #Redirect to post detail view
Related
I have created detailed views for the posts in my Django-based blog using the DetailView generic class and everything works fine so far. The problem, however, is that I have a field in my post model that is used to set the status of the posts (active, inactive, blocked) and I only want to render the detailed views when the status is active. If anyone knows of a way to achieve this, please let me know and I ask that you be as detailed as possible.
views.py
from .models import Post
from django.views.generic import DetailView
class PostDetailView(DetailView):
model = Post
context_object_name = 'post'
template_name = 'blog/post-detail.html'
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['title'] = Post.objects.filter(slug=self.object.slug).first()
return context
In your DetailView you can filter the queryset, for example you can filter with:
from django.views.generic import DetailView
class PostDetailView(DetailView):
queryset = Post.objects.filter(active=True)
# …
The DetailView will retrieve the element based on the primary key and/or slug on the queryset, so if the element is not in the filtered queryset, then you will retrieve a 404 error.
Here we assume that the Post model has an active field:
class Post(models.Model):
# …
active = models.BooleanField()
# …
Given the field and values are different, you should of course filter the queryset accordingly.
I'm kind of new to django, I'm working on a project currently. It is a website where people can look for houses to rent. Users will be able to create accounts, search for houses to rent and create listings about the houses they want to rent out.
I created a model to save all the information about houses that users want to rent out. I need to filter this information and display each user's listing on their profile. I have searched online but no solution yet.
Really need help.
models.py
from django.db import models
from django.contrib.auth.models import User
class Myhouses(models.Model):
Available = 'A'
Not_Available = 'NA'
Availability = (
(Available, 'Available'),
(Not_Available, 'Not_Available'),
)
name_of_accomodation = models.CharField(max_length=200)
type_of_room = models.CharField(max_length=200)
house_rent = models.IntegerField()
availability = models.CharField(max_length=2, choices=Availability, default=Available,)
location = models.CharField(max_length=200)
nearest_institution = models.CharField(max_length=200)
description = models.TextField(blank=True)
image = models.ImageField(upload_to='profile_image')
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='author')
def __str__(self):
return self.name_of_accomodation
view.py
class ListingByUser(LoginRequiredMixin, generic.ListView):
model = Myhouses
template_name ='houses/ListingByUser.html'
paginate_by = 10
def get_queryset(self):
return Myhouses.objects.filter(author=self.request.user)
urls.py
from django.conf.urls import url, include
from . import views
from django.contrib.auth.models import User
urlpatterns = [
url(r'^addlisting/$', views.addlisting, name='addlisting'),
url(r'^mylisting/', views.ListingByUser.as_view(), name='ListingByUser')
]
Template
<ul>
{% for houses in myhouses_list %}
<li>{{ houses.name_of_accomodation }}</li>
{%endfor %}
</ul>
Taking a quick view of your code, there is something that stuck me on your ListingByUser view: you override the get method only to set some attributes that are normaly defined as class attributes. That also could be preventing your view to actually get your models out of the database (via calling the get_queryset method) and rendering a proper response.
Edit
I found there's also a problem linking your template to the response the ListingByUser view is rendering. As far as I know, Django views doesn't look into the variable template_name for getting the response's template. But it does call a method get_template_names which returns a list of template names given as strings.
Try to modify it in this way:
views.py
class ListingByUser(LoginRequiredMixin, generic.ListView):
model = Myhouses
template_name ='myhouses/listing_by_user.html'
paginate_by = 10
def get_queryset(self):
return Myhouses.objects.filter(author=self.request.user)
def get_template_names(self):
return [self.template_name]
I would like to create a mutli-step form in Django that only submits the data for processing at the end of all the steps. Each step needs to be able to access and display data that we entered in previous step(s).
Is there a way to do this with Django? Django's Form-Wizard can't handle this basic functionality.
Of course there's a way to do this in Django.
One way is to hold your values in session until you submit them at the end. You can populate your forms using values held in session if you return to previous step.
With some searching, you may find an app that someone has already written that will do what you want, but doing what you need isn't hard to do with Django, or any other framework.
Example, ignoring import statements:
#models/forms
class Person(models.Model):
fn = models.CharField(max_length=40)
class Pet(models.Model):
owner = models.ForeignKey(Person)
name = models.CharField(max_length=40)
class PersonForm(forms.ModelForm):
class Meta:
model = Person
class PetForm(forms.ModelForm):
class Meta:
model = Pet
exclude = ('owner',)
#views
def step1(request):
initial={'fn': request.session.get('fn', None)}
form = PersonForm(request.POST or None, initial=initial)
if request.method == 'POST':
if form.is_valid():
request.session['fn'] = form.cleaned_data['fn']
return HttpResponseRedirect(reverse('step2'))
return render(request, 'step1.html', {'form': form})
def step2(request):
form = PetForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
pet = form.save(commit=False)
person = Person.objects.create(fn=request.session['fn'])
pet.owner = person
pet.save()
return HttpResponseRedirect(reverse('finished'))
return render(request, 'step2.html', {'form': form})
We'll assume that step2.html has a link to go back to step1.html.
You'll notice in the step1 view I'm pulling the value for fn from session that was set when the form was saved. You would need to persist the values from all previous steps into the session. At the end of the steps, grab the values, create your objects and redirect to a finished view, whatever that might be.
None of this code has been tested, but it should get you going.
You can easily do this with the form wizard of django-formtools. A simple example would be something like the following.
forms.py
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
views.py
from django.shortcuts import redirect
from formtools.wizard.views import SessionWizardView
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
do_something_with_the_form_data(form_list)
return redirect('/page-to-redirect-to-when-done/')
urls.py
from django.conf.urls import url
from forms import ContactForm1, ContactForm2
from views import ContactWizard
urlpatterns = [
url(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
]
I'm hoping this is just an issue of my poor regex understanding.
I'm attempting to use the exact code on Django 1.9's generic views to build a blog and personal site, and, down to the testing, here's where I run into trouble:
def test_post_page(self):
post = PostModelFactory()
first_post = Post.objects.all()[0]
post_url = first_post.get_absolute_url()
print(post_url)
response = self.client.get(post_url, follow=True)
self.assertEqual(response.status_code, 200)
So, through that print statement, I determined models.Post.get_absolute_url() was returning my homepage URL. Here's models.py:
class Post(models.Model):
title = models.CharField(max_length=200)
subtitle = models.CharField(max_length=200, default="")
pub_date = models.DateTimeField(auto_now_add=True)
text = models.TextField()
slug = models.SlugField(max_length=40,unique=True)
def get_absolute_url(self):
return "%s/" % (self.slug)
Should it come up, I copied down what the generic views documentation has, so my Detailview in /blog/urls.pyis as follows:
url(r'^(?P<slug>[-\w]+)/$', PostDetailView.as_view(), name='post-detail'),
Same of views.py:
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context
As far as I can tell, my get_absolute_url() function is simply not doing what I think it's doing, let alone what the regex in urls.py expects it to do.
Also: Is there anyone who can fully explain how slugfield works? I know it's a keyword generator to create a url, but I'm not sure how it works (or doesn't, as in this example).
Then, finally, in both a tutorial that I'm quasi-following alongside the documentation, and the documentation, itself, I'm not fully understanding where the variable names in templates are coming from (my understanding is that the request hit's the URL, which generates the data from views.py). The "ListView" object in the template shares the model name, "post" (or "article" in the documentation), where its pageview at the bottom is accessed simply through "page_obj", and the "DetailView" object is simply called "object". I also may be having a problem with paginating my ListView, ( which is identical to the documentation example, but with the extra line paginate_by = 2 right above get_context_data.
Thank you.
EDIT:
I've included PostModelFactory:
class PostModelFactory(DjangoModelFactory):
class Meta:
model = Post()
django_get_or_create = (
'title',
'subtitle',
'text',
'pub_date',
)
title = 'This is a test.'
subtitle = 'This is only a test.'
text = 'Madness? This is Sparta.'
pub_date = timezone.now()
def __init__(self):
self.save()
Edit: The issue turned out to be the lack of a slug in the PostModelFactory.
Ideally, you should use reverse in get_absolute_url, instead of hardcoding it.
from django.core.urlresolvers import reverse
class Post(models.Model):
...
def get_absolute_url(self):
return reverse('post-detail', args=[self.slug])
If you do hardcode the URL, it should contain a leading slash.
def get_absolute_url(self):
return "/%s/" % (self.slug)
If first_post.get_absolute_url is returning the homepage url with your current get_absolute_url, that suggests that the slug is an empty string.
New to Django, so please bear with me.
I've been fighting my way through the basic tutorial and documentation, but haven't found a satisfactory answer.
I have two models:
class Venue(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('venue_detail', kwargs={'pk': self.pk})
class Space(models.Model):
venue = models.ForeignKey('Venue', null=True)
name = models.CharField(max_length=200)
description = models.TextField(max_length=500)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('space_detail', kwargs={'pk': self.pk})
A space belongs to a Venue. That's all fine.
What I want to do, is to create a Space from a VenueDetail view, and use that Venue as the foreign key (i.e no dropdown select on the Space ModelForm).
However I haven't got as far as trying to use the current venue instance in the space form (is that called context? Sorry, coming from Rails), as I'm stuck on the form redirecting part.
views.py
class VenueDetail(FormView):
model = Venue
template_name = 'venues/detail.html'
form_class = SpaceForm
def get_context_data(self, **kwargs):
context = super(VenueDetail, self).get_context_data(**kwargs)
context['form'] = SpaceForm
return context
class SpaceCreate(CreateView):
model = Space
template_name = 'spaces/space_form.html'
fields = '__all__'
forms.py
class SpaceForm(ModelForm):
class Meta:
model = Space
fields = '__all__'
venue_urls.py
urlpatterns = [
url(r'^$', VenueListing.as_view(), name='venue_listing'),
url(r'^(?P<pk>[0-9]+)/$', VenueDetail.as_view(), name='venue_detail'),
url(r'^create/$', VenueCreate.as_view(), name='venue_create')
]
space_urls.py
urlpatterns = [
url(r'^$', SpaceListing.as_view(), name='space_listing'),
url(r'^(?P<pk>[0-9]+)/$', SpaceDetail.as_view(), name='space_detail'),
url(r'^create/$', SpaceCreate.as_view(), name='space_create')
]
If the user is creating a space from a VenueDetail view, at the moment if they have any errors they are being given the form with errors showing, but at the standard SpacesCreate url. Obviously this is happening because the form POSTs to that URL which then calls SpaceCreate.as_view(). Really the user would expect to be at the same URL if the form failed. If the form validates fine, at the moment it goes to the correct place which is 'space/pk' of the created space, which is fairly standard.
Essentially what I'm asking is what is the Django way to include a form to create one model, on it's parents show/detail page with all the expected behaviour. Perhaps I should be using a custom View for this form action when on that Venue detail page? One that specifically redirects to the same Venue detail page if the form post doesn't validate?
Thanks in advance for any help
UPDATE
I used a normal function view in the end, and ended up with something like this:
views.py
def new_space(request, venue_id):
venue = Venue.objects.get(id = venue_id)
context = {'form': SpaceForm, 'venue': venue}
if request.method == 'POST':
form = SpaceForm(request.POST)
if form.is_valid():
new_space = form.save(commit=False)
new_space.venue = venue
new_space.save()
return HttpResponseRedirect(reverse('space_listing'))
else:
return render(request, 'spaces/new_space.html', {'form': form})
else:
form = SpaceForm()
return render(request, 'spaces/new_space.html', context)
urls.py
url(r'^(?P<venue_id>[0-9]+)/new_space/$', views.new_space, name='new_space')
and all seems to be working well.