Python Django Form submit button not working as desired - python

I have been trying to learn Django.
I am stuck on this form part. A form has been created that allows the user to create an Album object where they can fill in the Artist, Album Name, Genre and upload an Album Logo. When I fill in the fields and then click submit, it should then redirect me to the details page for that particular Album that just got created. But nothing appears to happen when clicking the submit button and the object does not get created.
Here is the models.py code that contains an Album class with 4 fields; artist, album_name, genre and album_logo.
from django.db import models
from django.urls import reverse
# Create your models here.
class Album(models.Model):
artist = models.CharField(max_length=250)
album_name = models.CharField(max_length=500)
genre = models.CharField(max_length=100)
album_logo = models.ImageField()
def get_absolute_url(self):
return reverse('music:detail', kwargs={'pk':self.pk})
def __str__(self):
return self.album_name + " - " + self.artist
class Song(models.Model):
album = models.ForeignKey(Album, on_delete=models.CASCADE)
file_type = models.CharField(max_length=100)
song_title = models.CharField(max_length=250)
is_favourite = models.BooleanField(default=False)
def __str__(self):
return self.song_title
Here is the album_form.html code which contains the actual form. I have not used crispy_forms as I am not familiar with Bootstrap though I know CSS.
{% extends 'music/base.html' %}
{% block title %}Add a New Album{% endblock %}
{% block body %}
<form class="formContainer" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
{% if field.label != 'Album logo' %}
<label for="field{{ forloop.counter }}">{{ field.label }}</label>
<input type="text" id="field{{ forloop.counter }}" name="" value="">
{% else %}
<label for="field{{ forloop.counter }}">{{ field.label }}</label>
<input type="file" id="field{{ forloop.counter }}" name="" value="" accept="image/*">
{% endif %}
<br>
{% endfor %}
<input type="submit" id="submitBtn" name="" value="Add">
</form>
{% endblock %}
This is views.py code where I have made use of class based views and not function based views.
from django.views import generic
from .models import Album, Song
# Create your views here.
class IndexView(generic.ListView):
template_name = 'music/index.html'
queryset = Album.objects.all()
context_object_name = 'all_albums'
class DetailView(generic.DetailView):
model = Album
template_name = 'music/detail.html'
class AlbumCreate(generic.CreateView):
model = Album
fields = ['artist', 'album_name', 'genre', 'album_logo']
def form_valid(self, form):
return super().form_valid(form)
and finally this is my urls.py code:
from django.urls import path, include
from . import views
app_name='music'
urlpatterns = [
#/music/
path('', views.IndexView.as_view(), name='index'),
#/music/5/
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
#/music/album/add/
path('album/add/', views.AlbumCreate.as_view(), name='album-add')
]
After clicking the submit button in the form, it should take me to the "detail" url for the primary key of the Album that got created. Am I missing something here?

In your views.py you need to override the get_success_url function in your CreateView and pass the id as an argument while redirecting.
class AlbumCreate(generic.CreateView):
model = Album
fields = ['artist', 'album_name', 'genre', 'album_logo']
def form_valid(self, form):
return super().form_valid(form)
def get_success_url(self):
return reverse('music:detail', args=(self.object.id,))

Seems you forgot to put action to your <form> tag
Try this
<form class="formContainer" action='{% url 'music:album-add'%}' method="post" enctype="multipart/form-data">
Edit: Also add success url using get_success_url function in your AlbumCreate view to redirect user to album detail page, like was mentioned in above answer
from django.urls import reverse_lazy
...
class AlbumCreate(generic.CreateView):
...
def get_success_url(self, **kwargs):
return reverse_lazy('music:detail', args = (self.object.id,))

Related

Django - Auto populating Many-To-Many-Field relation

I am trying to make a form that auto populates a many-to-many relation for my user model. The goal is to have a submit button that adds the views instance object (the SingelWorkout object) to a many-to-many field relation within my user model.
The view accurately displays the correct object, and the form appears as intended within the template. I do not wish for the user to see the many-to-many field selection. Aside from the submit button, I am trying to have all logic to occur on the backend. How would I assign an object instance to a field within a form? Would this occur in the views.py or the forms.py?
Here is why my user model looks like:
class FitnessUser(AbstractUser):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField(max_length=60)
age_category = models.ForeignKey(
AgeGroup,
on_delete=models.CASCADE,
blank=True,
null=True
)
goal = models.IntegerField(default=1 ,choices=Purpose.choices)
weight = models.CharField(max_length=30)
height = models.CharField(max_length=30)
gender = models.ForeignKey(
Gender,
on_delete=models.CASCADE,
blank=True,
null=True
)
exercise_frequency = models.IntegerField(default=1 ,choices=Frequency.choices)
template_id = models.ForeignKey(
Workout_template,
on_delete=models.CASCADE,
blank=True,
null=True
)
completed_workouts = models.ManyToManyField(SingleWorkout)
def get_absolute_url(self):
return reverse('detail', args=[self.id])
This is my form in forms.py:
class CustomWorkoutChangeForm(UserChangeForm):
class Meta(UserChangeForm):
model = FitnessUser
fields = ('completed_workouts',)
exclude = ('completed_workouts',)
UserChangeForm.password = None
This is how my view looks:
class WorkoutUpdateView(LoginRequiredMixin, UpdateView):
model = SingleWorkout
template_name = 'workout/daily_view.html'
form_class = CustomWorkoutChangeForm
success_url = reverse_lazy("template")
def get_context_data(self, **kwargs):
context = super(WorkoutUpdateView, self).get_context_data(**kwargs)
context['workout'] = SingleWorkout.objects.get(slug = self.kwargs['slug'])
return context
My html template looks like this:
{{workout}}
<br>
workout:
<br>
{{ workout.exercise_1 }}
<br>
{{ workout.description_1 }}
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Confirm">
</form>
Figured out a solution. I created a view that gets the instance object based on the objects url slug, and also gets the user by its pk. From there is adds the instance object to the users many to many field, then redirects back to the previous page.
New view created:
def update_workout_data(request, slug):
workout = SingleWorkout.objects.get(slug=slug)
endUser = FitnessUser.objects.get(pk = request.user.pk)
endUser.completed_workouts.add(workout)
endUser.save()
return redirect(reverse('daily', kwargs={'slug':workout.slug}))
Updated HTML appearance. I've also altered the html and its detail view so that the update link will redirect to a separate update view, depending on the need to add/remove the relation.
{% block content %}
Daily View
<br>
{{exercise}}
<br>
workout:
<br>
<br>
{% if exercise.name in workouts %}
<h5>Workout Already Completed</h5>
<form method="POST" action="{% url 'remove' slug=exercise.slug %}">
{% csrf_token %}
<button type="submit">Reset</button>
</form>
{% else %}
<form method="POST" action="{% url 'update' slug=exercise.slug %}">
{% csrf_token %}
<button type="submit">Complete</button>
</form>
{% endif %}
{% endblock content %}
Updated Detail View
def get_context_data(self, **kwargs):
context = super(WorkoutDetailView, self).get_context_data(**kwargs)
user = FitnessUser.objects.get(pk = self.request.user.pk)
context['exercise'] = SingleWorkout.objects.get(slug = self.kwargs['slug'])
context['workouts'] = {}
for workout in user.completed_workouts.all():
context['workouts'][workout.name] = workout
return context

Django: post form and post list on the same page

I make a site with multiple users, making posts with images and ability to add/remove friends.
So it's easy to make two different pages for post list and creating a new one. But of course it looks better when you can read posts and make new at the same place.
As I understand (learn django for less than a month), I can't connect 2 views to the same url, so the most logical way I see is to join 2 views in one, I also tried to play with template inheriting to render post form by including template, but actually it doesn't work.
Below you can see my views, Post model, and templates. Thank you for attention.
views.py:
from braces.views import SelectRelatedMixin
from . import models
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin
class PostList(SelectRelatedMixin, generic.ListView):
model = models.Post
select_related = ('user',)
class CreatePost(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
models.py:
import misaka
class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE, related_name = 'posts')
posted_at = models.DateTimeField(auto_now = True)
post_message = models.TextField()
message_html = models.TextField(editable = False)
post_image = models.ImageField(upload_to = 'postpics', blank = True)
def __str__(self):
return self.post_message
def save(self, *args, **kwargs):
self.message_html = misaka.html(self.post_message)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('posts:all')
class Meta:
ordering = ['-posted_at']
unique_together = ['user', 'post_message']
urls.py:
app_name = 'posts'
urlpatterns = [
path('', views.PostList.as_view(), name = 'all'),
path('new/', views.CreatePost.as_view(), name = 'create'),
]
post_form.html (template, that allows to make a new post, which will be seen in post_list.html):
{% extends 'posts/post_base.html'%}
{% block post_content %}
<div class="post-form">
<form action="{% url 'posts:create' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.post_message }}</p>
<p>{{ form.post_image }}</p>
<input id='post-submit' type="submit" value="Post">
</form>
</div>
{% endblock %}
post_list.html:
{% extends 'posts/post_base.html'%}
{% block post_content %}
<div class="post-container">
{% for post in post_list %}
<div class="current-post-container">
{% include 'posts/_post.html'%}
</div>
{% endfor %}
</div>
{% endblock %}
_post.html(pages, which render by Misaka):
<div class="post-info">
<h5 id='post-owner' >{{post.user.first_name}} {{post.user.last_name}}</h5>
<h6>{{ post.posted_at }}</h6>
<p>{{ post.message_html|safe }}</p>
<div>
<img class='post-image' src="/media/{{ post.post_image }}" alt="">
<div>
{% if user.is_authenticated and post.user == user and not hide_delete %}
<a href="{% url 'posts:delete' pk=post.pk %}" title = 'delete'>Delete</a>
{% endif %}
</div>
</div>
</div>
post_base.html:
{% extends 'base.html' %}
{% block content%}
{% block prepost %}{% endblock %}
{% block post_content %}{% endblock %}
{% block post_post %}{% endblock %}
{% endblock %}
EDIT:
Task was solved. I added two template_name strings to both of my views, so now they look like:
CreatePost in views.py:
class CreatePost(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
PostList in views.py:
class PostList(SelectRelatedMixin, generic.ListView):
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
You can put the post_create_form on the same page as post_list_view there is no need to make a separate view for post creation but You need to make ones for editing and deleting.
You can give all of these views the same HTML page with different URLs.
Using template_name = 'example/example.html' ,in Class_Based_Views.
I hope I understand your problem if not clarify more why you can't join two views in one.
def posts(request):
posts = Post.objects.all()
form = PostForm(request.POST or None, request.FILES or None)
if request.method == "POST":
if form.is_valid():
...
context={
'posts' : page_obj,
'create_or_update_post_form' : form,
}
return render(request, 'post/posts.html',context)
Do you struggle to do this in Class-based-view?
You can do easily with django class based views.
Create views as
from django.views.generic import ListView
from django.views.generic.edit import CreateView
class ModelCreate(CreateView):
model = ModelName
fields = ['field1', 'field2']
template_name = 'same_page.html'
success_url = reverse_lazy('list_view')
class ModelList(CreateView, ListView):
model = ModelName
fields = ['field1', 'field2']
paginate_by = 5
template_name = 'same_page.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['form'] = self.get_form()
return context
# If form post redirect to ModelCreate View
def post(self, request, *args, **kwargs):
return ModelCreate.as_view()(request)
app/urls.py
from django.urls import path
from app import views
path('list', views.ModelList.as_view(), name='list_view'),
path('add', views.ModelCreate.as_view(), name='add_view'),
Finally in templates/same_page.html
<div class="row">
<div class="col-sm-5">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
<button type="submit" value="submit" class="btn btn-primary btn-sm float-right">Submit</button>
</form>
</div>
<div class="col-sm-5">
{% if object_list %}
{% for object in object_list %}
<p>{{object.field1}}</p>
<p>{{object.field2}}</p>
{% endfor %}
{% endif %}
</div>
</div>
Hope, this helps.

Django DeleteView doesn't delete the object from data

I've just learned CreateView UpdateView and DeleteView models and then I got all of them working. I can change my object I can delete them, but after deleting an object I try to create another and creates one like the before hasn't been deleted. giving me the following pk of the deleted before
it still creates de object although the pk at the moment should be 3, I think after I click the delete button and confirm delete it isn't deleting from data.
These are my models:
from django.db import models from django.urls import reverse
class Dreams (models.Model):
titulo = models.CharField(max_length=100)
objetivo = models.CharField(max_length=100)
imagem = models.CharField(max_length=100)
def get_absolute_url(self):
return reverse ('webdeve:index', kwargs={'pk': self.pk})
def __str__(self):
return self.titulo + ' - ' + self.objetivo
class Which (models.Model):
lets = models.ForeignKey(Dreams, on_delete=models.CASCADE)
make = models.CharField(max_length=100)
it = models.CharField(max_length=100)
favorite = models.BooleanField(default=False)
def __str__(self):
return self.make
my views.py:
from django.views import generic
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from .models import Dreams, Which
from django.urls import reverse_lazy
class IndexView (generic.ListView):
template_name = 'index.html'
def get_queryset(self):
return Dreams.objects.all()
class DetailView (generic.DetailView):
model = Dreams
template_name = 'detail.html'
class DreamCreate (CreateView):
model = Dreams
fields = ['titulo', 'objetivo', 'imagem']
template_name = 'dreams_form.html'
class DreamUpdate (UpdateView):
model = Dreams
fields = ['titulo', 'objetivo', 'imagem']
template_name = 'dreams_form.html'
class DreamDelete (DeleteView):
model = Dreams
template_name= ('dreams_confirm_delete.html')
success_url= reverse_lazy('webdeve:index')
my urls.py:
from django.conf.urls import url
from webdeve import views
app_name = 'webdeve'
# Dreams
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
# Dreams/detail
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
# Dreams/detail/add
url(r'^dream/add/$', views.DreamCreate.as_view(), name='dream-add'),
# Dreams/detail/Update
url(r'^dream/(?P<pk>[0-9]+)/$', views.DreamUpdate.as_view(), name='dreams-uptdate'),
# Dreams/detail/detete
url(r'^dream/(?P<pk>[0-9]+)/delete/$', views.DreamDelete.as_view(), name='dreams-delete'),
]
And my index.html with delete button:
<!-- linkando css no html -->
{% extends 'base.html' %}
{% block nav %}
<ul>
{% for Dreams in object_list %}
<img src={{ Dreams.imagem }}>
<li> {{ Dreams.titulo }} - {{ Dreams.objetivo }} <li>
<!--delete BUTTON-->
<form action="{% url 'webdeve:dreams-delete' Dreams.id %}">
{% csrf_token %}
<input type="hidden" name="dreams_id" value="{{ Dreams.id }}" method="post" style="display: inline" >
<button type="submit" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-trash"></span>
</button>
</form>
{% endfor %}
</ul>
{% endblock %}
DeleteView only deletes your object on POST, not GET. As a result, you need to use method="post" in your form since GET would render this confirm_delete.html again.
<form method="post" action="">
...
</form>
If you can get into DeleteView on GET, action="" is all you need to POST.

Django error when trying to display the sign up form

So I have this form which extends the User and I just want to allow the student to create and account and be able to select the courses from the course list.But when I try, I get the error:
__init__() takes 1 positional argument but 2 were given
I can't find any solution to this yet. I need some advice.
These are my files:
{% block body %}
<div class="row">
<div class="col-md-8 col-sm-10 col-12">
<h2>Sign up as a {{ user_type }}</h2>
<form method="post" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}">
{{ form }}
<button type="submit" class="btn btn-success">Sign up</button>
</form>
</div>
</div>
{% endblock %}
class User(AbstractUser):
is_student = models.BooleanField(default=False)
is_teacher = models.BooleanField(default=False)
path('signup/', views.StudentSignUpView, name='signup')
class StudentSignUpForm(UserCreationForm):
attended_courses = forms.ModelMultipleChoiceField(
queryset=Course.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=True
)
class Meta(UserCreationForm.Meta):
model = User
#transaction.atomic
def save(self):
user = super().save(commit=False)
user.is_student = True
user.save()
student = Student.objects.create(user=user)
student.attended_courses.add(*self.cleaned_data.get('attended_courses'))
return user
class StudentSignUpView(CreateView):
model = User
form_class = StudentSignUpForm
template_name = 'signup_form.html'
def get_context_data(self, **kwargs):
kwargs['user_type'] = 'student'
return super().get_context_data(**kwargs)
def form_valid(self, form):
user = form.save()
login(self.request, user)
return redirect('index')
Class-based views need to be referenced in urls.py via their as_view method.
path('signup/', views.StudentSignUpView.as_view(), name='signup')

How create a custom form in django with database values

model.py
class Venue(models.Model):
venue_Name = models.CharField(max_length=100)
place = models.CharField(max_length=50)
rent = models.IntegerField()
parking_area = models.IntegerField()
class Decoration(models.Model):
rate = models.IntegerField()
I have printed the values in database as radio buttons what i want to do is that i want to get the total sum i.e venue.rent + decoration.rate and print it in another page What shoud i give in form action I'm not that familiar with forms.
html
<form action="{% %}" method="post">
{% for venue in venues %}
<input type="radio" name=venue value=venue.rent />{{ venue.venue_Name}}
{% endfor %}
{% for decoration in decorations %}
<input type="radio" name=decor value=decoration.rate />{{ decoration.rating }}
{% endfor %}
<input type="submit" value=" " />
</form>
what should i write in view and urls to get the sum
You can use Django's form for validation and parsing. For that you would set up a form like so:
forms.py
from django import forms
class TotalSumForm(forms.Form):
venue = forms.ModelChoiceField(queryset=Venue.objects.all(), required=True)
decoration = forms.ModelChoiceField(
queryset=Decoration.objects.all(), required=True)
def get_total(self):
# send email using the self.cleaned_data dictionary
return self.cleaned_data['venue'].rent +\
self.cleaned_data['decoration'].rate
And then using a class based view, add the result to context upon submission.
views.py
from myapp.forms import TotalSumForm(
from django.views.generic.edit import FormView
class TotalCost(FormView):
template_name = 'your_template.html'
form_class = TotalSumForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
total_result = form.get_total()
# return back to your_template.html with the total in context
return self.render_to_response(self.get_context_data(
form=form, total=total_result))
The urls are pretty simple:
urls.py
from django.conf.urls import patterns, url
import myapp.views
urlpatterns = patterns(
'',
url(r'^total_calc/$', myapp.views.TotalCost.as_view(), name='calculate_total'),
)
Your html could be modified like so
your_template.html
<html>
<body>
<h1>TEST SUCCESFULL!</h1>
{% if total %}
<p>Total cost for venue and decoration: {{ total }}</p>
{% endif %}
<form action="{% url 'calculate_total' %}" method="post">
{{ form.as_p }}
<input type="submit" value="Calculate Total" />
</form>
</body>
</html>

Categories