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.
Related
I need solution for deleting Profesor by his 'name':
class Profesor(models.Model):
user = models.OneToOneField(User, null=True, blank=True, on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True)
suername = models.CharField(max_length=200, null=True)
Imagine is to ask user on html page to input 'name' of Profesor that he want to delete
And then in views.py to delete Profesor by that 'ime'
How can i do that?
I try to create form where i get 'name':
class getName(forms.Form):
name = forms.IntegerField()
And then i views.py:
def obrisiProfesora(request):
form = getName()
if request.method=="POST":
form = getName(requst.POST)
form.save()
context = {'form':form}
profesor = Profesor.objects.filter(name=form.name)
return render(request, 'brisanje_profesora.html', context)
But i dont know how to contionue this
Class names are idiomatically PascalCase in Python, so getName would be GetName. However, NameForm might be more apt, since that's what it is, a form for a name.
Similarly, functions are idiomatically snake_case.
Why would the name field be an integer field, when the name field in the model is a CharField?
form.save() isn't a thing for forms that aren't modelforms. That being said, I don't think you've tried that code very hard, since it'd crash at requst not being a thing, nor context having been defined.
All in all, with a plain function view you might want something like
class NameForm(forms.Form):
name = forms.CharField(required=True)
def delete_professor(request):
form = NameForm(data=(request.POST if request.method == "POST" else None))
if form.is_bound and form.is_valid():
professor = Profesor.objects.get(name=form.cleaned_data["name"])
professor.delete()
return render(request, 'brisanje_profesora.html', context)
or with a class-based view (so you don't need to do so much work yourself),
class DeleteProfessorView(FormView):
form_class = NameForm
template_name = 'brisanje_profesora.html'
def form_valid(self, form):
professor = Profesor.objects.get(name=form.cleaned_data["name"])
professor.delete()
Some error handling is elided from both solutions.
Dear StackOverFlow community,
Basing on a built-in user User model I've created my own model class called "ModelOfParticularPerson". The structure of it looks like this:
class ModelOfParticularPerson(models.Model):
user = models.OneToOneField(User)
nickname = models.CharField(max_length=100, null=True, unique=False)
uploaded_at = models.DateTimeField(auto_now_add=True, null=True)
email_address = models.EmailField(max_length=200, blank=False, null=False, help_text='Required')
description = models.CharField(max_length=4000, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True, blank=True)
Unfortunately, after loggin in with the usage of particular account, whenever I am trying to reedit the profile, I do get following error:
"Model of particular person with this User already exists."
Any advice is priceless.
Thanks.
ps.
views.py:
[..]
#method_decorator(login_required, name='dispatch')
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
model = ModelOfParticularPerson
form_class = ModelOfParticularPersonForm
success_url = "/accounts/profile/" # You should be using reverse here
def get_object(self):
# get_object_or_404
return ModelOfParticularPerson.objects.get(user=self.request.user)
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
def post(self, request):
form = ModelOfParticularPersonForm(self.request.POST, self.request.FILES)
if form.is_valid():
print("FORM NOT VALID!")
profile = form.save(commit=False)
profile.user = self.request.user
profile.save()
return JsonResponse(profile)
else:
return render_to_response('my_account.html', {'form': form})
urls.py:
urlpatterns = [
[..]
url(r'^login/$', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
url(r'^accounts/profile/$', ProfileUpdateView.as_view(template_name='my_account.html'), name='my_account'),
]
forms.py
class ModelOfParticularPersonForm(ModelForm):
class Meta:
model = ModelOfParticularPerson
fields = '__all__'
widgets = {
'user':forms.HiddenInput(),
'uploaded_at':forms.HiddenInput(),
'created':forms.HiddenInput(),
}
You need to pass the instance to the form, otherwise Django will try to create a new object when you save it.
def post(self, request):
form = ModelOfParticularPersonForm(instance=self.get_object(), self.request.POST, self.request.FILES)
...
You should try to avoid overriding get or post when you're using generic class based views. You can end up losing functionality or having to duplicate code. In this case, it looks like you can remove your post method. In the form_valid method you can return a JsonResponse. You shouldn't have to set form.instance.user if you are updating an existing object.
def form_valid(self, form):
profile = form.save()
return JsonResponse(profile)
Finally, you should leave fields like user and uploaded_at out of the model form instead of making them hidden fields.
You're creating new forum in your post method of view, but you're not passing existing model object to it. That leads to creation of new model, which fails, because object already exists.
Instead of overwritting post method, put saving of object inside is_valid method and use already provided form object (passed to you by method parameter).
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
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 have a website which catalogs local hikes. Users can "log" that they have completed these hikes. I have both of these models+forms working as intended. Right now, though, in order to log a hike, you have to select the hike from a long list which contains all the hikes in the database. I'd like to be able to pre-populate that field so that if you are coming from the detail page of the hike in question, then that field is filled in with the hike.
Here's some code:
models.py:
model Hike(models.Model):
name = CharField(max_length=255)
slug = models.SlugField(unique=True)
...other fields...
model UserLog(models.Model):
hike = models.ForeignKey(Hike, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
forms.py:
class LogHike(forms.ModelForm):
class Meta:
model = UserLog
fields = ('hike', 'date', ... other fields)
views.py:
def hike_detail(request, slug):
hike = Hike.objects.get(slug=slug)
log = UserLog.objects.filter(hike__slug=slug)
... more stuff here ...
return render(request, 'hikes/hike_detail.html' {
'hike': hike,
'log': log,
})
def log_hike(request):
if request.method == "POST":
form = LogHike(request.POST)
if form.is_valid():
obj = form.save(commit=False)
userid = request.user
obj.user = request.user
obj.save()
return redirect('user_profile', uid=userid.id)
else:
form = LogHike()
return render(request, 'log_hike.html', {'form': form})
So if a user is viewing the "hike_detail" view, I want to have a link that sends them to the "log_hike" view, but with the "Hike" field pre-populated based on the "Hike" that they came from. I think it might have something to do with the instance function? But I don't have a ton of experience with it. Is there an easy way to pass the data from the referring page in order to pre-populate the form?
You probably want to override your ModelForm __init__ method:
def __init__(self, *args, **kwargs):
super(LogHike, self).__init__(*args, **kwargs)
if 'hike' in kwargs:
self.fields['hike'].value = kwargs['hike']
Now all you need is another view which accepts a parameter passed and you're set. Extend your urls.py for that and then do something like:
def log_hike_with_pre_set_hike(request, *args, **kwargs):
if request.method == 'POST':
# see your code
else:
form = LogHike(hike=kwargs['hike'])
return render(request, 'log_hike.html', {'form': form})
Untested code, you might have to adapt it, I come from class-based views so it might be different for you.
You can pre-populate that form in log_hike when the request.method is get in the same way as when in post.
form = LogHike({'hike':hike_id})
The other thing is form where you'll take the hike_id. But that can come from request.GET for example.