I am a newbie in Django pretty much. Since I want to step my game up now, I want to dive into generic class based views. I already figured out how to use template views, but now I want to go for the create views.
I have these forms:
class LearningObjectiveForm(ModelForm):
class Meta:
exclude = ['trainee']
These models:
class LearningObjective(models.Model):
trainee = models.ForeignKey(Trainee, blank = True)
learning_objective = models.TextField()
class Trainee(models.Model):
username = models.TextField()
...
class Topic(models.Model):
trainee = models.ForeignKey(Trainee, blank = True)
learning_objective = models.ManyToManyField(LearningObjective, blank = True, null = True)
topic = models.TextField()
And this create view:
class CreateLearningObjective(CreateView):
model = LearningObjective
form = LearningObjectiveForm
def form_valid(self, form):
self.object = form.save(commit=False)
if self.request.user.is_authenticated():
self.object.trainee = self.request.user
self.object.save()
return super(CreateLearningObjective, self).form_valid(form)
My urls:
from django.conf.urls import patterns, url
from programm import views
from .views import LearningObjectiveView, CreateLearningObjective
urlpatterns = patterns('',
url(r'^$', views.index, name='index'),
url(r'^learning_objective/$', LearningObjectiveView.as_view(), name = 'learning_objective'),
url(r'^learning_objective_add/$', CreateLearningObjective.as_view(), name = 'learning_objective_add'),
)
Template:
<form action="{% url 'learning_objective_add' %}" method="post">
{% csrf_token %}
<textarea name="learning_objective" rows="4"></textarea>
<p><select name="topic" size="6" multiple>
{% for lO in learning_objectives %}
{% for t in lO.topic_set.all %}
<option>{{ t.topic }}</option>
{% endfor %}
{% endfor %}
</select></p>
<input type="submit" value="Absenden"/>
</form>
unfortunately when I try to submit a post, I just receive a Cannot assign None: "LearningObjective.trainee" does not allow null values. Error. Can anybody please help me?? I am really stuck and clueless what I should do right here :/
Change your urls.py to:
urlpatterns = patterns('',
url(r'^$', views.index, name='index'),
url(r'^learning_objective/$', LearningObjectiveView.as_view(), name = 'learning_objective'),
url(r'^learning_objective_add/$', CreateLearningObjective.as_view(), name = 'learning_objective_add'),
)
The error is that your second url ('^learning_objective') intercepts the post request to the third url. This is why you should add $ at the end of regex.
And / slash is also required for POST views because of APPEND_SLASH setting.
Related
I've followed the tutorial here to implement a basic search function: https://learndjango.com/tutorials/django-search-tutorial
I'd like to extend that tutorial by making the search function visible on the results page, allowing for repeated search. However, when I do this I can't get the search form to show up on the search results page. The search button shows up, but not the field to provide input.
Relevant code:
home.html:
<div name="searchform">
<form action="{% url 'search_results' %}" method="get">
{{ form }}
<input type="submit" value="Search">
</form>
</div>
{% block content %}
{% endblock %}
search_results.html:
{% extends home.html}
{% block content %}
<h1>Search Results</h1>
<ul>
{% for city in object_list %}
<li>
{{ city.name }}, {{ city.state }}
</li>
{% endfor %}
</ul>
{% endblock %}
Views.py:
from django.db.models import Q
from django.views.generic import TemplateView, ListView, FormView
from .models import City
class HomePageView(FormView):
template_name = 'home.html'
form_class = SearchForm
class SearchResultsView(ListView):
model = City
template_name = 'search_results.html'
def get_queryset(self):
query = self.request.GET.get('q')
object_list = City.objects.filter(
Q(name__icontains=query) | Q(state__icontains=query)
)
return object_list
urls.py:
from django.urls import path
from .views import HomePageView, SearchResultsView
urlpatterns = [
path('search/', SearchResultsView.as_view(), name='search_results'),
path('', HomePageView.as_view(), name='home'),
]
forms.py:
from django import forms
class SearchForm(forms.Form):
q = forms.CharField(label='', max_length=50,
widget=forms.TextInput(attrs={'placeholder': 'Search Here'})
)
Any advice on how I might troubleshoot this sort of issue (or if I'm blatantly doing something un-django-y) would be greatly appreciated.
You're using ListView which is a Generic display view.
You need to use get method, then you can pass the form to make the search again and stay on the same page.
class SearchResultsView(View):
template_name = 'search_results.html'
form_class = SearchForm
def get(self, request):
form = self.form_class()
query = self.request.GET.get('q')
context = {}
context['form'] = form
context['cities'] = City.objects.filter(
Q(name__icontains=query) | Q(state__icontains=query)
)
return render(self.request, self.template_name, context)
You can achieve the same result with ListView but is better if you use other based view class.
You can check the doc. here
class HomePageView(FormView):
template_name = 'home.html'
form_class = SearchForm # This line!
Remember to also apply the form_class attribute to SearchResultsView, otherwise, no forms will be interpreted. The submit button only shows up because it's not a part of the rendered form.
Codes in views
I am new to django I couldn't able to rectify where it went wrong can anyone please help me on this.
class UpdateVote(LoginRequiredMixin,UpdateView):
form_class = VoteForm
queryset = Vote.objects.all()
def get_object(self,queryset=None):
vote = super().get_object(queryset)
user = self.request.user
if vote.user != user:
raise PermissionDenied('can not change another user vote')
return vote
def get_success_url(self):
movie_id = self.object.movie.id
return reverse('core:movie_detail', kwargs={'pk':movie_id})
def render_to_response(self, context, **response_kwargs):
movie_id = context['object'].id
movie_detail_url = reverse('core:movie_detail',kwargs={'pk':movie_id})
return redirect(to=movie_detail_url)
class MovieDetail(DetailView):
queryset = Movie.objects.all_with_prefetch_persons()
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if self.request.user.is_authenticated:
vote = Vote.objects.get_vote_or_unsaved_blank_vote(movie=self.object,user=self.request.user)
if vote.id:
vote_url_form = reverse('core:UpdateVote',kwargs={'movie_id':vote.movie.id,'pk':vote.id})
else:
vote_url_form = (reverse('core:create_vote',kwargs={'movie_id':self.object.id}))
vote_form = VoteForm(instance=vote)
ctx['vote_form'] = vote_form
ctx['vote_url_form'] = vote_url_form
return ctx
Codes in form.py
I have used this form to link with UpdateView
from django import forms
from django.contrib.auth import get_user_model
from .models import Movie,Vote
class VoteForm(forms.ModelForm):
user = forms.ModelChoiceField(widget=forms.HiddenInput,queryset=get_user_model().objects.all(),disabled=True)
movie = forms.ModelChoiceField(widget=forms.HiddenInput,queryset = Movie.objects.all(),disabled=True)
value = forms.ChoiceField(widget=forms.RadioSelect,choices=Vote.VALUE_CHOICE)
class Meta:
model = Vote
fields = ('value','user','movie',)
urls.py
This is the url mapping for the view.
from django.contrib import admin
from django.urls import path
from .views import MovieList,MovieDetail,PersonDetail,CreateVote,UpdateVote
app_name = 'core'
urlpatterns = [
path('movies/', MovieList.as_view(), name='movie_list'),
path('movie/<int:pk>/', MovieDetail.as_view(), name='movie_details'),
path('person/<int:pk>/', PersonDetail.as_view(), name='person_details'),
path('movie/<int:movie_id>/vote/', CreateVote.as_view(), name='create_vote'),
path('movie/<int:movie_id>/vote/<int:pk>', UpdateVote.as_view(), name='UpdateVote'),
]
HTML template
This is the template I used.
{% block sidebar %}
<div>
{% if vote_form %}
<form action="{{vote_form_url}}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ vote_form.as_p }}
<button class="btn btn-primary" type="submit" >Vote</button>
</form>
{% else %}
<p>Login to vote for this movie</p>
{% endif %} </div> {% endblock %}
The problem caused because your form was sent to another path which doesn't allow POST request. vote_form_url is not which you added in the view context, use vote_url_form instead.
...
<form action="{{ vote_url_form }}" method="post" enctype="multipart/form-data">
...
Btw, your MovieDetail view can get rid of if self.request.user.is_authenticated: by using LoginRequiredMixin like UpdateVote view.
Hope that helps!
This question already has answers here:
Django - Login and redirect to user profile page
(2 answers)
Closed 4 years ago.
New to Django and running into a problem. I'm trying to configure my project so that when a user logs in, they are redirected to account/profile/ + username + / . I'm assuming I'm missing something fundamental but haven't been able to nail it down yet. My appreciation in advance for any help.
edited
Forgot to mention I have LOGIN_REDIRECT_URL = 'accounts:profile' and that the error message I'm getting is:
NoReverseMatch at /accounts/login/
Reverse for 'profile' with no arguments not found. 1 pattern(s) tried: ['accounts/profile/(?P[-a-zA-Z0-9_]+)/$']
end edit
models.py
class User(auth.models.User, auth.models.PermissionsMixin):
def __str__(self):
return '#{}'.format(self.username)
class Meta:
db_table = 'users'
urls.py
app_name = 'accounts'
urlpatterns = [
path('signup/', views.SignUp.as_view(), name='signup'),
path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
path('profile/<slug:slug>/', views.Profile.as_view(), name='profile')
]
views.py
class SignUp(CreateView):
form_class = forms.UserCreateForm
success_url = reverse_lazy('login')
template_name = 'accounts/signup.html'
class Profile(DetailView, LoginRequiredMixin):
model = models.User
slug_field = 'username'
def get_context_data(self, request, **kwargs):
context = super().get_context_data(**kwargs)
context['username'] = request.user.username
return context
def get_success_url(self):
return reverse_lazy('accounts:profile', kwargs={'slug': self.username})
login.html
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block bodyblock %}
<div class="container">
<h1>Header Here</h1>
<form class="login-form" method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Log In">
</form>
</div>
{%endblock%}
profile.html
{% extends 'base.html' %}
{% block bodyblock %}
<div class="container">
<h1>Welcome {{user.username}} !</h1>
</div>
{% endblock %}
by default, django uses a next GET parameter to determine where to redirect after logging in. If you want to disable this, set redirect_field_name to None (its value is not a URL as RHSmith159 states - it is a field name, the default is next). Also use settings.LOGIN_REDIRECT_URL to specify a standard redirect target (default is /accounts/profile/) - this is what you are currently observing.
I using Django with widgets to get some user input but I can't seem to get it to display even thou the code seems almost identical to examples online.
forms.py
from django import forms
class PickerIDForm(forms.Form):
pickeID = forms.NumberInput()
views.py
def get_id(request):
template = loader.get_template('metrics/lance1.html')
def get(self, request):
form = PickerIDForm()
return render(request, 'self.template', {'form': form})
context ={
}
return HttpResponse(template.render(context,request))
urls.py
from django.urls import path
from . import views
from . import mark_views
app_name = 'metrics'
urlpatterns = [
path('', views.index, name='index'),
path('test/', views.get_id, name='get_id'),
]
test.html
{% extends 'base.html' %}
{% block content %}
<p>User Input</p>
<form method = "post" >
{% csrf_token %}
{{form.as_p}}
<button type="submit"> Submit </button>
</form>
{% endblock %}
I'm never directly calling the get function as defined in views.py that to me seems to be a possible source of the input fields not showing up when I load up test.html
At what point do you link the disparate parts together? Because it seems I'm missing something.
You have defined the widget instead of the field in your form.
To fix that replace pickeID = forms.NumberInput() with pickeID = forms.IntegerField()
And also write your view like this:
def get_id(request):
form = PickerIDForm()
return render(request, 'metrics/lance1.html', {'form': form})
in my main application folder, the URLs.py has the following code.
urlpatterns = patterns('',
(r'^login/$', 'django.contrib.auth.views.login'),
# (r'^catalog/$', home),
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
{ 'document_root' : 'C:/SHIYAM/Personal/SuccessOwl/SOWL0.1/SOWL/SOWL/static'}),
# (r'^admin/', include('django.contrib.admin.urls')),
(r'^catalog/', include('CATALOG.urls')),
(r'^accounts/', include('registration.urls')),
(r'^$', main_page),
)
I have a seperate app (Folder) called "CATALOG" and it's URLs.py has the following code:
urlpatterns = patterns('SOWL.catalog.views',
(r'^$', 'index', { 'template_name':'catalog/index.html'}, 'catalog_home'),
(r'^category/(?P<category_slug>[-\w]+)/$', 'show_category', {'template_name':'catalog/category.html'},'catalog_category'),
(r'^product/(?P<product_slug>[-\w]+)/$', 'show_product', {'template_name':'catalog/product.html'},'catalog_product'),
(r'^enter_product/$',enter_product),
)
Under catalog folder, I have forms.py
from django import forms
from CATALOG.models import Product
class ProductAdminForm(forms.ModelForm):
class Meta:
model = Product
def clean_price(self):
if self.cleaned_data['price'] <= 0:
raise forms.ValidationError('Price must be greater than zero.')
return self.cleaned_data['price']
class Product_Form(forms.Form):
name = forms.CharField(label='name', max_length=30)
slug = forms.SlugField(label='Unique Name for the URL', max_length=30)
brand = forms.CharField(label='Unique Name for the URL', max_length=30)
price = forms.DecimalField(label='Price',max_digits=9,decimal_places=2)
old_price = forms.DecimalField(max_digits=9,decimal_places=2,initial=0.00)
quantity = forms.IntegerField()
description = forms.CharField()
meta_keywords = forms.CharField(max_length=255)
meta_description = forms.CharField(max_length=255)
categories = forms.CharField(max_length=255)
user = forms.IntegerField()
prepopulated_fields = {'slug' : ('name',)}
and views.py
from CATALOG.forms import *
def enter_product(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.clean_data['username'],
password=form.clean_data['password1'],
email=form.clean_data['email']
)
return HttpResponseRedirect('/')
else:
form = RegistrationForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response(
'catalog/enter_product.html',
variables
)
and under "\templates\catalog" folder, I have "enter_product.html", which has the following code:
{% extends "base.html" %}
{% block title %}blah{% endblock %}
{% block head %}blah{% endblock %}
{% block content %}
<form method="post" action=".">
{{ form.as_p }}
<input type="submit" value="Enter" />
</form>
{% endblock %}
but when I go to localhost:8000/catalog/enter_product/ it says:
The view CATALOG.views.enter_product didn't return an HttpResponse object.
why is that? Thanks for your help.
Toronto
Your return render_to_response... is indented inside of if request.method == 'POST'. So if it is not a post, the view function returns nothing. Try unindenting your return statement by one level.