I have a page for teachers to input the marks and registration number of the students..
After input, it gets stored in the database and the students can fill a form which asks for D.O.B and registration number and get's the marks based on that particular registration from the database..
But when I use post request for the students, it shows form is invalid and says that, the registration number already exists..
My views.py:
from django.shortcuts import render
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from .models import Mark
from django.contrib.auth.views import LoginView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import FormView
from .forms import ViewResultForm, AddResultForm
from django.contrib import messages
class ViewResultFormView(FormView):
template_name = 'main/home.html'
form_class = ViewResultForm
success_url= 'result'
def form_valid(self, form):
global registration_number
global dob
registration_number = form.cleaned_data['registration_number']
dob = form.cleaned_data['dob']
return super(ViewResultFormView, self).form_valid(form)
class MarkListView(ListView):
model = Mark
template_name = "main/result.html"
context_object_name = 'result'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['result'] = context['result'].get(registration_number=registration_number, dob=dob)
return context
class MarkCreateView(LoginRequiredMixin, CreateView):
model = Mark
template_name = "main/add.html"
form_class = AddResultForm
def form_valid(self, form):
total_10th = ((form.cleaned_data['class_10_sub_1'] + form.cleaned_data['class_10_sub_2'] + form.cleaned_data['class_10_sub_3'])/300)*30
total_11th = ((form.cleaned_data['class_11_English'] + form.cleaned_data['class_11_Maths'] +form.cleaned_data['class_11_Physics'] +form.cleaned_data['class_11_Chemistry'] +form.cleaned_data['class_11_Comp_Bio'])/500) * 30
total_12th = ((form.cleaned_data['class_12_English'] + form.cleaned_data['class_12_Physics'] +form.cleaned_data['class_12_Chemistry'] +form.cleaned_data['class_12_Maths']+ form.cleaned_data['class_12_Comp_Bio'] + form.cleaned_data['class_12_practicals_Physics'] + form.cleaned_data['class_12_practicals_Chemistry'] + form.cleaned_data['class_12_practicals_Comp_Bio'] )/500)*40
result = total_10th + total_11th + total_12th
total = form.save(commit=False)
total.teacher_name = self.request.user
total.result = result
total.save()
message = messages.success(self.request, f'Result added successfully')
return super().form_valid(form)
class CustomLoginView(LoginView):
template_name = 'main/login.html'
fields = '__all__'
redirect_authenticated_user = True
def get_success_url(self):
return reverse_lazy('add')
home.html:
{% extends 'main/base.html' %}
{%load crispy_forms_tags %}
{% block content %}
<div class="container mt-5 card shadow p-3 mb-5 bg-white rounded">
<legend>Enter your credentials</legend>
<form method="POST">
{% csrf_token %}
{{ form | crispy }}
<input class='btn btn-outline-info' type="submit" value="Submit">
</form>
</div>
{% endblock content %}
result.html:
{% extends 'main/base.html' %}
{% block content %}
<div class="container">
{{ result.student_name }}
<br>
{{ result.dob }}
<br>
{{ result.result }} %
</div>
{% endblock content %}
So once the teacher enters the marks of the student, I calculate the results and store it in the database.. But bcz the teacher has registered a particular registration number, it shows form is invalid when a student tries to enter the same registration number in the form.. I want the registration number to be unique..
So if I have to use GET in home.html, how to access the values of the form?
You should rewrite your ViewResultFormView.form_valid, because you do not want form.save() to be called in the super call. You should retrieve registration_number and dob from the form and then fill a context for the template with a results of the corresponding student.
Also I do not understand what are those global hacks for? If you need to store that info for a several site pages, then you should implement your own authentication for students which will store their auth tokens/cookies somewhere, and then use that authentication info to allow/deny access to certain pages.
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.
I am a Django beginner and I started working on my first project. I implemented a model "extendedUser" with a medic_code field, extending User. It appears to be a problem when displaying the medic_code in a template. It doesn't display the actual property of the user, but the default value: "".
Template
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<div class="media">
<img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
<div class="media-body">
<h2 class="account-heading">{{ user.username }}</h2>
<p class="text-secondary">{{ user.email }} </p>
<p class="text-secondary">{{ user.medic_code }}</p> (empty string here)
</div>
</div>
<!-- FORM HERE -->
</div>
{% endblock content %}
models.py:
from django.db import models
from django.contrib.auth.models import User
class extendedUser(User):
medic_code = models.CharField(max_length=20, default='')
users/forms.py:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from users.models import extendedUser
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
medic_code = forms.CharField(max_length=20)
class Meta:
model = extendedUser
fields = ['username', 'email', 'medic_code', 'password1', 'password2']
views.py:
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm
def register(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST)
if form.is_valid():
user = form.save()
#user.refresh_from_db()
user.medic_code = form.cleaned_data.get('medic_code')
user.save()
username = form.cleaned_data.get('username')
messages.success(request, f'Your account has been created! You are now able to log in! Your medic code {user.medic_code}') #correct value displayed here
return redirect('login')
else:
form = UserRegisterForm()
return render(request, 'users/register.html', {'form': form})
#login_required
def profile(request):
user = request.user
return render(request, 'users/profile.html', {'user':user})
Also after I create a user and display the medic_code field in a Django shell for that user, the proper value is displayed. What may be problem?
Thanks!
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!
I am trying to create a Django app that allows a user to select a file name from a drop down menu and upload the file. Regardless of what the user has named their file, I will receive that file saved as whatever they selected on the drop down plus the date. I cannot seem to get my drop downs to show. Hours of reading the documentation, multiple stack overflow posts, and trial and error have not helped. * Does anyone know how to get the user-selected drop down value from views.py to models.py to be inserted into the uploaded file's path? *
On another note, I am sure there are other issues with my code, being a Django newb, so please point them out if you see them.
models.py
from django.db import models
from datetime import datetime
import os
def update_filename(instance, filename):
path = "documents/"
format = '{0}_{1}.csv'.format(vc, datetime.today().strftime('%Y%m%d'))
#note that vc is defined in views.py but I am not sure how to transfer this to models.py
#*************************************************************
return os.path.join(path, format)
class Document(models.Model):
docfile = models.FileField(upload_to= update_filename) #/%Y/%m/%d')
#value_chains = (('coffee', 'coffee'),('tea', 'tea'),)
dropdown = models.CharField(max_length=20, choices=(('coffee', 'coffee'),('tea', 'tea'),))
forms.py
from django import forms
from .models import Document
def validate_file_extension(value):
if not value.name.endswith('.csv'):
raise forms.ValidationError("Only CSV file is accepted")
class DocumentForm(forms.ModelForm):
docfile = forms.FileField(label='Select a file', validators=[validate_file_extension])
value_chains = (('coffee', 'coffee'),('tea', 'tea'),)
dropdown = forms.ChoiceField(choices=value_chains, required=True)
class Meta:
model = Document
fields = ['dropdown']
views.py
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views.generic.edit import CreateView
from .models import Document
from .forms import DocumentForm
def list(request):
# Handle file upload
if request.method == 'POST':
#bind the data to the form
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
#**** vc is defined here ************************************
vc = form.cleaned_data['dropdown']
newdoc = Document(docfile = request.FILES['docfile'])
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('file_upload_app.views.list'))
# if a GET (or any other method) we'll create a blank form:
else:
form = DocumentForm() # A empty, unbound form. This is what we can expect to happen the first time we visit the URL.
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'file_upload_app/list.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
list.html
<form action="{% url "list" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.dropdown }}
<p>
<h6>
Note: the uploaded file must be in
<b>.CSV</b>
format and contain a column labeled "A" which contains
<em>only</em>
numbers.
</h6>
</p>
<p>{{ form.non_field_errors }}</p>
<p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
<p>
{{ form.docfile.errors }}
{{ form.docfile }}
</p>
<p><input type="submit" value="Upload"/></p>
</form>
<!-- List of uploaded documents -->
<p>
{% if documents %}
<div class="col-sm-12" align="center" >
<ul>
<p>Files on server:</p>
{% for document in documents %}
<li>{{ document.docfile.name }}</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="col-sm-12" align="center" >
<p>No documents.</p>
</div>
I recommend you use Djano's CreateView for your view. In your case you would have
Class UploadDocumentView(CreateView):
model = Document
When you click submit on your form, and you have the action pointing to the correct view, it will create a new Document with the fields you provided.
Your template should have this in it, the url should be the name you gave your url. I recommend against using "list" as your url because that is a special name in python and Django has it's own concept of ListView.
<form id="upload_document" method="POST" action="{% url 'document:upload' %}">
{{ form }}
<input class="btn btn-primary" id="upload_document_button" type="submit" value="Upload Document"/>
</form>
Sometimes seeing a working example is useful. The one below uses Django's UpdateView. The UpdateView will use the get_object method to determine which object in the database to modify. It knows what model to look at because it is set to model = UserProfile, and it knows what values to save because you set them in your form.
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
primary_key=True)
company_identifier = models.CharField(max_length=5, blank=False)
employee_id = models.CharField(max_length=20, null=True, blank=False)
forms.py
def get_company_choices():
company_list = [('','-------')]
companies = models.Company.objects.values_list('id', 'name').order_by('name')
company_list = list(chain(company_list, companies))
return company_list
class EmployerForm(forms.ModelForm):
employee_id_confirm = forms.CharField(label="Confirm Employee ID", required=True)
company_identifier = forms.ChoiceField(choices=get_company_choices, label="Company")
class Meta:
model = models.UserProfile
fields = ('company_identifier', 'employee_id')
labels = {
'employee_id': "Employee ID",
}
views.py
#method_decorator(login_required, name='dispatch')
class EmploymentUpdate(SuccessMessageMixin, UpdateView):
model = UserProfile
template_name = 'employee/update_employment.html'
form_class = forms.EmployerForm
success_url = reverse_lazy('employee:home')
success_message = "Employment Updated."
def get_object(self):
return self.model.objects.get(pk=self.request.user.userprofile.pk)
I found the solution, denoted by the **** below:
models.py:
from django.db import models
from datetime import datetime
import os
value_chains = (
('coffee', 'coffee'),
('tea', 'tea'),
)
def update_filename(instance, filename):
path = "documents/"
#*************************************************************
#use instance to access the instance of Document's CharField function, which is defined below
format = '{0}_{1}.csv'.format(instance.dropdown, datetime.today().strftime('%Y%m%d'))
#*************************************************************
return os.path.join(path, format)
class Document(models.Model):
docfile = models.FileField(upload_to= update_filename)
dropdown = models.CharField(max_length=20, choices=value_chains)
class Meta:
permissions = (("access_file_upload_app", "Access file upload tool."),)
views.py:
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.conf import settings
from .models import Document
from .forms import DocumentForm
#login_required
def doc_upload_view(request):
if request.user.has_perm('file_upload_app.access_file_upload_app') and settings.VC_ENABLED == 'Y':
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
#********************************************************
# make sure to pass the dropdown value to the Document call:
newdoc = Document(docfile = request.FILES['docfile'], dropdown = form.cleaned_data['dropdown'])
#********************************************************
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('file_upload_app.views.doc_upload_view'))
else:
form = DocumentForm() # An empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'file_upload_app/doc_upload_view.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
else:
return redirect('/home/')
forms.py:
from django import forms
from .models import Document
def validate_file_extension(value):
if not value.name.endswith('.csv'):
raise forms.ValidationError("Only CSV file is accepted")
class DocumentForm(forms.ModelForm):
value_chains = (('coffee', 'coffee'),('tea', 'tea'),)
docfile = forms.FileField(label='Select a file', validators=[validate_file_extension])
dropdown = forms.ChoiceField(choices=value_chains, required=True )
class Meta:
model = Document
fields = ('dropdown',)
doc_upload_view.html:
<!-- Upload form. Note enctype attribute! -->
<form action="{% url "doc_upload_view" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.dropdown }}
<p>{{ form.non_field_errors }}</p>
<p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
<p>
{{ form.docfile.errors }}
{{ form.docfile }}
</p>
<p><input type="submit" value="Upload"/></p>
</form>
<!-- List of uploaded documents -->
<p class="top-space">
{% if documents %}
<div class="col-sm-12" align="center" >
<ul>
<p>Files on server:</p>
{% for document in documents %}
<li>{{ document.docfile.name }}</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="col-sm-12" align="center" >
<p>No documents.</p>
</div>
{% endif %}
</p>
I am using an extended user model from all-auth. I am able to pull both extended model fields (like organization), as well as the standard user fields (like user.email) easily on all templates except one.
Because I struggled to build an update form (as I'm still new to Django), I ended up using properties/instances. This worked. I can edit orgnization. But for some reason on this template I cannot call user.email. I've tried all variations of it, from RequestContext to failed attempts at passing it in other ways.
Model:
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
organization = models.CharField(max_length=100)
def __str__(self):
return self.user.organization
# I got this from an old tutorial:
User.profile_p = property(lambda u: Profile.objects.get_or_create(user=u)[0])
View:
from django.shortcuts import render, render_to_response
from django.http import HttpResponseRedirect
from django.template.context_processors import csrf
from .forms import UserProfileForm
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from .models import Profile
#login_required
def user_profile(request):
if request.method == 'POST':
form = UserProfileForm(request.POST, instance=request.user.profile_p)
if form.is_valid():
form.save()
return HttpResponseRedirect('/base_home.html')
else:
logged_user = request.user
profile = logged_user.profile_p
form = UserProfileForm(instance=logged_user.profile_p)
args = {}
args.update(csrf(request))
args['form'] = form
return render_to_response('profile.html', args)
Template:
{% extends 'base.html' %}
<title>{% block title %}Admul:{% endblock %}</title>
{% block content %}
<div id="cd_content">
<div class="container-fluid">
<div class="row-fluid">
<h3>Account Details for: {{ user.email }}</h3>
<div class="widget-box">
<div class="widget-content nopadding">
{% for field in form %}
{{field.error}}
{% endfor %}
<form method="post" action="/cust_profile/profile/">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Edit ยป</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
For some reason, user.email or many variations thereof (like {{request.user.email}} ) does not work in this template. It, however, works perfectly fine in all other templates. Is it something to do with my view cancelling the RequestContext info from being passed?
Also, thanks in advance if you see me making any other silly errors that can be fixed.
You should not be using render_to_response in your view. That does not run context processors, and so does not add the request or user variables. Use render instead:
return render(request, 'profile.html', args)
Also note that CSRF will be added by a context processor, so there is no need to add it manually as you are doing.