Key error django - passing additional stuffs with django form - django

I have my models like this:
class Photo(models.Model):
title = models.CharField(max_length=255, blank=True)
file = models.FileField(default='default.jpg')
page = models.ForeignKey(Page, on_delete=models.CASCADE)
class Page(Group):
name = models.IntegerField()
in my forms.py, to create a form for posting multiple images, I created this: I also override get_form_kwargs for passing the 'page' thing with the form as you can see that the Photo model has a page foreignkey.
class PhotoForm(forms.ModelForm):
def get_form_kwargs(self):
kwargs = super(PhotoForm, self).get_form_kwargs()
kwargs['page'] = Page.objects.get(pk=self.GET['pk'])
return kwargs
class Meta:
model = Photo
fields = ('file', )
In my main urls.py, I say like:
path('page/<int:pk>/photos/', include('photos.urls'))
and in photos apps urls.py, I say:
path('progress-bar-upload/', views.ProgressBarUploadView.as_view(), name='progress_bar_upload')
and in my photos apps views.py I say:
class ProgressBarUploadView(View):
model = Photo
def get(self, request, **kwargs):
photos_list = Photo.objects.all()
page = get_object_or_404(Page, page=self.kwargs['page'])
return render(self.request, 'photos/progress_bar_upload/index.html', {'photos': photos_list, 'page':page})
def post(self, request):
time.sleep(1)
form = PhotoForm(self.request.POST, self.request.FILES)
if form.is_valid():
photo = form.save()
data = {'is_valid': True, 'name': photo.file.name, 'url': photo.file.url}
else:
data = {'is_valid': False}
return JsonResponse(data)
Then when in my html I call the page,
{{page}}
Then when I go to 'localhost:/page/1/photos/progress-bar-upload/' I get this error:
KeyError at /page/7/photos/progress-bar-upload/
'page'
So what can I do? Can someone help me? Any help will be much much appreciated.

Related

How to call django forms inlineformset into django templates

i am new to django and learning some from stackoverflow. Now i am creating a website for post with images and title. I found ways to connect my two models (images and post) at https://stackoverflow.com/a/62158885/13403211. it is working fine when i add post from admin. But i want to know how can i add those inlineformset fields into my template for user to fill in.Does anyone knows??
Here is the code i found. I copy the same code in my app to try.
models.py
class Item(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="items")
name = models.CharField(max_length=100)
class ItemImage(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
img = models.ImageField(default="store/default_noitem.jpg", upload_to=get_image_dir)
forms.py
from django import forms
from django.forms.models import inlineformset_factory
from .models import Item, ItemImage
class ItemImageForm(forms.ModelForm):
class Meta:
model = ItemImage
exclude = ()
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ["name",]
ItemImageFormSet = inlineformset_factory(
Item, ItemImage, form=ItemImageForm,
fields=['img'], extra=3, can_delete=True # <- place where you can enter the nr of img
)
views.py
class ItemCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
template_name = "items/add_item_form.html"
success_message = 'Item successfully added!'
form_class = ItemForm
def get_context_data(self, **kwargs):
data = super(ItemCreateView, self).get_context_data(**kwargs)
data['form_images'] = ItemImageFormSet()
if self.request.POST:
data['form_images'] = ItemImageFormSet(self.request.POST, self.request.FILES)
else:
data['form_images'] = ItemImageFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
form_img = context['form_images']
with transaction.atomic():
form.instance.user = self.request.user
self.object = form.save()
if form_img.is_valid():
form_img.instance = self.object
form_img.save()
return super(ItemCreateView, self).form_valid(form)
I have search on google and i did not find any related to this. Or am i doing wrong? Can someone help me?

2 Forms on same model not saving as same user - Django

I'm creating a questionnaire / survey, and have two forms (Model Form) built on the same model. These forms are called on separate views, but when saved they appear as separate users in the database. I'm not sure how to get them so save as the same user, I am already using the ' post = form.save(commit=False), post.user = request.user, post.save()' method to save the forms.
EDIT: Added in an attempt to save to the same instance
Model:
class QuizTakers(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
industry_choices = (
(1, 'Service'),
(2, 'Hospitality'),
(3, 'Wholesale/Retail'),
(4, 'Manufacturing'),
(5, 'Agriculture')
)
industry = MultiSelectField(choices=industry_choices, max_length=1, max_choices=1)
company_name = models.CharField( max_length=100)
email = models.EmailField(blank=True)
score = models.FloatField(default=0)
completed = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.company_name
Forms:
# Form for getting company name
class QuizTakerForm(forms.ModelForm):
class Meta:
model = QuizTakers
fields = ['company_name']
# Form for getting company industry
class QTIndustryForm(forms.ModelForm):
class Meta:
model = QuizTakers
fields = ['industry']
Views:
# view for getting company name
def start(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = QuizTakerForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
request.session['company_name'] = form.cleaned_data['company_name']
post = form.save(commit=False)
post.user = request.user
post.save()
# redirect to a new URL:
return HttpResponseRedirect('industry/')
# if a GET (or any other method) we'll create a blank form
else:
form = QuizTakerForm()
return render(request, 'ImpactCheck/start.html', {'form': form})
# view for getting industry
class IndustryView(FormView):
template_name = 'ImpactCheck/industry.html'
form_class = QTIndustryForm
success_url = '1/'
def get(self, request):
company_name = request.session['company_name']
this_user=QuizTakers.objects.filter(company_name=company_name).order_by('-timestamp').first()
form=self.form_class(instance=this_user)
company_name = request.session['company_name']
return render(request, 'ImpactCheck/industry.html', {'form': form, 'company_name': company_name})
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
post = form.save(commit=False)
post.user = self.request.user
post.save()
return HttpResponseRedirect('/1')
Firstly, in your def start(request) function, you should consider adding the ID to request.session instead of the company name. Something along the lines of
def start(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = QuizTakerForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
form.instance.user=request.user
form.save()
request.session['obj_id'] = post.id
# redirect to a new URL:
return HttpResponseRedirect('industry/')
Now you can use that id to get both the name of your company, as well as the object.
In your IndustryView(FormView), if you're having trouble with the form instances, it's better to use UpdateView instead of the FormView (Be sure to import UpdateView first)
class IndustryView(UpdateView):
template_name = 'ImpactCheck/industry.html'
model = QuizTakers
fields = ['industry']
success_url = '/1'
def get_object(self):
return QuizTakers.objects.get(pk=self.request.session.get('obj_id'))
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['company_name'] = QuizTakers.objects.get(pk=self.request.session.get('obj_id'))
return ctx
We use the get_context_data method since you need the company_name in your template. The get_object method in this view, tells django which object is to be updated. By default, it grabs the pk from the url (as a url parameter). But since we store our id in the session, we need to explicitly define this function.
Also, since we switched to UpdateView, you no longer need the QTIndustryForm either.

Django's get_initial() method not working as desired

I am using django's generic CreateView to build a comment system for my site. A user is allowed to do comment for a movie. Here is my Comment model-
class Comment(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="comments", on_delete=models.CASCADE)
body = models.TextField()
movie = models.ForeignKey(Movie, related_name="comments", on_delete=models.CASCADE)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
class Meta:
ordering = ('created',)
def __str__(self):
return "comment by {} on {}".format(self.user.first_name, self.movie)
Here is the CreateView i am using-
class AddComment(LoginRequiredMixin, CreateView):
form_class = CommentForm
def get_initial(self):
initial = super().get_initial()
#for providing initial values to the form
initial['user'] = self.request.user.id
initial['movie'] = self.kwargs['movie_id']
return initial
def get_success_url(self):
movie_id = self.kwargs['movie_id']
return reverse('detail', kwargs={'pk':movie_id})
def render_to_response(self, context=None, **response_kwargs):
movie_id = self.kwargs['movie_id']
return redirect(to = reverse('detail', kwargs={'pk':movie_id}))
Here is the commentform -
class CommentForm(forms.ModelForm):
user = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=get_user_model().objects.all())
movie = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=Movie.objects.all())
class Meta:
model = Comment
fields = ('user','movie', 'body')
I am trying to associate a comment to a user and a movie. I thus used get_initial() method to fill the form with initial data because user and movie were not present in the posted data. But somehow always form.is_valid() turns out to be false. I don't know where i went wrong. Please Help.
If it helps i tried to debug my program by printing out the value of kwargs that were being used to instantiate the form object by overriding the get_form_kwargs function-
{
'initial': {'user': 1, 'movie': 2}, 'prefix': None,
'data': <QueryDict: {'csrfmiddlewaretoken': ['wFmkOMLAcIszMc17GsBsqPhyaZnJEXb0TRNteKd9sgjYKEF3jvqwsQ3Noik3DHq6'], 'body': ['best movie ever\r\n'], 'user': [''], 'movie': ['']}>, 'files': <MultiValueDict: {}>
}
Well, user and movie are Foreign key fields, so that they expect to receive object of related models as initials. You are trying to use pk(int) instead of these objects.
It should be like following:
def get_initial(self):
initial = super().get_initial()
#for providing initial values to the form
initial['user'] = self.request.user
initial['movie'] = Movie.objects.get(pk=movie_id)
return initial.copy()

How to make sure users only get to DetailView, Listview and UpdateView their own created objects

I've created a simple app where logged in users can can submit a session for a conference, view the results of their submission, view a list of their submissions, and edit their submissions (they should not have access to other users' submissions). I'm using django's class-based views (CreateView, DetailView, ListView, UpdateView).
I'm struggling with the permissions however. All the views, except updateview, work but if I type in the url directly using a non-logged in username I can see their submissions.
I also suspect that the permissions is the same reason I can't get the updateview to work.
What am I missing? And is there a better way to avoid using usernames and slugs in the Url? I can't seem to find any examples or tips in how to do this type of thing. I'm a beginner so probably miss some understanding of the fundamentals here and there.
I've tried to understand how the User model works because there I did manage to find a way to create, view and edit user details in a protected way. I relied on function views there though and can't seem to apply that approach to the submission app.
models.py
class Hsession(models.Model):
submitter = models.ForeignKey(User, related_name="submittersessions", on_delete=models.CASCADE)
submission_date = models.DateTimeField(auto_now=True)
session_title = models.CharField("session title", max_length=40, default='')
session_description = models.TextField("session description", max_length=350, default='')
slug = models.SlugField(allow_unicode=True, unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.session_title)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("submission:detail-single", kwargs={"username": self.submitter.username, "slug": self.slug})
urls:
urlpatterns = [
path("", views.CreateSubmission.as_view(), name="create"),
path("by/<username>/<slug>",views.SubmissionDetail.as_view(),name="detail-single"),
path("by/<slug>/edit",views.EditSubmission.as_view(), name="edit"),
path("by/<username>/",views.SubmissionList.as_view(), name="list"),
]
views.py
class CreateSubmission(LoginRequiredMixin, generic.CreateView):
fields = ('session_title', 'session_description', 'subject_category')
model = models.Hsession
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.submitter = self.request.user
self.object.save()
return super().form_valid(form)
class SubmissionList(LoginRequiredMixin, generic.ListView):
model = models.Hsession
template_name = "submission/user_hsession_list.html"
def get_queryset(self):
try:
self.hsession_submitter = User.objects.prefetch_related("submittersessions").get(
username__iexact=self.kwargs.get("username")
)
except User.DoesNotExist:
raise Http404
else:
return self.hsession_submitter.submittersessions.all()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["hsession_submitter"] = self.hsession_submitter
return context
class SubmissionDetail(LoginRequiredMixin, generic.DetailView):
model = models.Hsession
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(
submitter__username__iexact=self.kwargs.get("username")
)
class EditSubmission(LoginRequiredMixin, generic.UpdateView):
model = models.Hsession
fields = ('session_title', 'session_description', 'subject_category')
template_name = 'submission/hsession_update.html'
success_url = 'submission/hsession_detail.html'
forms.py
class UserSubmissionForm(ModelForm):
class Meta:
model = Hsession
fields = ['session_title','session_description', 'subject_category']
class EditSubmissionForm(ModelForm):
class Meta:
model = Hsession
fields = ['session_title','session_description', 'subject_category']
You should use UserPassesTestMixin like this
from django.contrib.auth.mixins import UserPassesTestMixin
class EditSubmission(UserPassesTestMixin,LoginRequiredMixin, generic.UpdateView):
model = models.Hsession
fields = ('session_title', 'session_description', 'subject_category')
template_name = 'submission/hsession_update.html'
success_url = 'submission/hsession_detail.html'
def test_func(self):
//should return true if he have access
if self.request.user.is_authenticated:
slug = self.kwargs['slug']
obj = self.model.objects.get(slug=slug)
login_user = self.request.user
return login_user.pk == obj.submitter.pk
else:
return False
for more information UserPassesTestMixin
from django.contrib.auth.mixins import UserPassesTestMixin
class EditSubmission(LoginRequiredMixin, UserPassesTestMixin, generic.UpdateView):
model = models.Hsession
fields = ('session_title', 'session_description', 'subject_category')
template_name = 'submission/hsession_update.html'
success_url = 'submission/hsession_detail.html'
def test_func(self):
self.object = self.get_object()
if self.request.user == self.object.user:
return True
else:
return False
I'm using "UserPassesTestMixin" like this and it is working.

Setting form fields in django class based generic view CreateView

I'm using django's CreateView to add images to a book. I pass the book's id to the class based view as a parameter in the url. Form fields such as book and language are not rendered on the template, rather they're obtained with the help of the book's id.
# views.py
class PictureCreateView(CreateView):
model = Upload
fields = "__all__"
book_id = None
def get_initial(self):
initial = super(PictureCreateView, self).get_initial()
initial = initial.copy()
self.book_id = self.kwargs['book_id']
book = Book.objects.get(id=self.book_id)
initial['book'] = book
initial['language'] = language
initial['uploader'] = self.request.user
return initial
# set book_id so it used in the template
def get_context_data(self, **kwargs):
context = super(PictureCreateView, self).get_context_data(**kwargs)
context['book_id'] = self.book_id
return context
def form_valid(self, form, **kwargs):
print('Form is valid')
self.object = form.save()
files = [serialize(self.object)]
data = {'files': files}
response = JSONResponse(data, mimetype=response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return super(PictureCreateView, self).form_valid(form)
def form_invalid(self, form):
print('Form invalid!')
print(form.errors)
data = json.dumps(form.errors)
return HttpResponse(content=data, status=400, content_type='application/json')
# models.py
class Upload(models.Model):
image = models.ImageField(upload_to=get_upload_path, help_text='Image to process')
uploader = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE, related_name='uploader')
language = models.ForeignKey(Language, models.CASCADE)
book = models.ForeignKey(Book, models.CASCADE)
The problem is that I get an error saying the form is invalid, and the fields uploader, book and language are required. How do I resolve this?
The initial data is used to display the defaults when the form is initially displayed. It isn't used when those values are missing from the submitted form data. If fields like book and uploader are set from the URL or logged-in user, then you should leave them out of the form completely, instead of setting them in the initial data. You can then set the values on the instance in the form_valid method before the form is saved.
from django.contrib.auth.mixins import LoginRequiredMixin
class PictureCreateView(LoginRequiredMixin, CreateView):
model = Upload
fields = ['other_field1', 'other_field2', ...] # leave out book, language and uploader
def form_valid(self, form):
self.book_id = self.kwargs['book_id']
book = Book.objects.get(id=self.book_id)
form.instance.book = book
form.instance.language = ????
form.instance.uploader = self.request.user
return super(
The LoginRequiredMixin makes sure that only logged-in users can access the view.
You may want to use get_object_or_404 to handle the case where book_id refers to a book that does not exist.
One thought, initial doesn't fill the model for submission. You need to do that in init
def __init__(self):
super(PictureCreateView, self).__init__()
self.fields['book'] = self.initial['book']
self.fields['uploader'] = self.initial['uploader']
self.fields['language'] = self.initial['book']
Or, if you don't want to set the fields, make sure they are optional in your original model:
class Upload(models.Model):
uploader = models.ForeignKey('uploader', on_delete=models.CASCADE, null=True, blank=True)
book = models.ForeignKey('book', on_delete=models.CASCADE, null=True, blank=True)
language = models.ForeignKey('language', on_delete=models.CASCADE, null=True, blank=True)