django: ForeignKeyField limit_choices_to "parents pk"? - django

I've a model (Parent model):
class Post(models.Model):
image = models.ImageField(upload_to='%Y/%m/%d')
title = models.CharField(max_length=200)
width = models.DecimalField(max_digits=3, decimal_places=0)
height = models.DecimalField(max_digits=3, decimal_places=0)
year = models.PositiveIntegerField()
def __str__(self):
return self.title
and another model (Child model):
class Addimg(models.Model):
post = models.ForeignKey('Post', null=True)
addimg = models.ImageField(upload_to='%Y/%m/%d')
def __str__(self):
return self.post
My Addimg Form:
class AddimgForm(forms.ModelForm):
class Meta:
model = Addimg
fields = ('post', 'addimg', 'width', 'height',)
views.py using the form:
def addimg(request, pk):
if request.method == "POST":
form = AddimgForm(request.POST, request.FILES)
post = get_object_or_404(Post, pk=pk)
if form.is_valid():
addimg = form.save(commit=False)
addimg.addimg = request.FILES['addimg']
addimg.save()
return redirect('blog.views.detail', pk=post.pk)
else:
form = AddimgForm()
return render(request, 'blog/edit.html', {'form': form})
And my Problem is that when I create a "Child model" my post field returns all instances of allready created Post models as choices. What I want is that it automatic only displays the one Post it is related to without choices. Is ForeignKey the right model for that?
Any Ideas how this could work. thanks

ForeignKey field is translated into ModelChoiceField inside a Django ModelForm. If you inspect that class you will notice that this type of field has an queryset attribute required. By default Django provides the full set of objects. You can override this inside your form __init__ method by providing the parent object the form will need.
Consider the following example code:
def addimg(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = AddimgForm(request.POST, request.FILES, post=post)
#...
else:
form = AddimgForm(post=post)
return render(request, 'blog/edit.html', {'form': form})
class AddimgForm(forms.ModelForm):
class Meta:
model = Addimg
fields = ('post', 'addimg', 'width', 'height',)
def __init__(self, *args, **kwargs):
post = kwargs.pop('post')
super(AddimgForm, self ).__init__(*args, **kwargs)
self.fields['post'].queryset = Post.objects.filter(id=post.id)

What you want to do is create a Many-to-one relationship. For example,
post = models.ForeignKey('Post', null=True)
This means you can filter on it for example,
Addimg.objects.filter(post=Post)
or
Post.objects.get(pk=1)
Post.addimg_set.filter(xyz=etc)
Read more here: https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/

Related

Formset not submitting uploaded picture?

I have a view to allow courses to created by users on my app, which am currently having a challenge uploading a course cover(pic) when users create a new course, when course is created all the fields of the course detail get submitted to the data base except the picture data, why is the picture not being submitted, can some one please help me. as this has been a challenge for me for the past week!
This is my view to create a new course!.
class OwnerMixin(object):
def get_queryset(self):
qs = super(OwnerMixin, self).get_queryset()
return qs.filter(owner=self.request.user)
class OwnerEditMixin(object):
def form_valid(self, form):
form.instance.owner = self.request.user
return super(OwnerEditMixin, self).form_valid(form)
class OwnerCourseMixin(OwnerMixin):
model = Course
fields = ['subject', 'title', 'slug','overview','pic']
success_url = reverse_lazy('courses:manage_course_list')
class OwnerCourseEditMixin(OwnerCourseMixin):
fields = ['subject', 'title','slug', 'overview','pic']
success_url = reverse_lazy('courses:manage_course_list')
template_name = 'manage/module/formset.html'
class ManageCourseListView(OwnerCourseMixin,ListView):
template_name ='courses/course_list.html'
class CourseCreateView(OwnerCourseEditMixin,OwnerEditMixin,CreateView,):
pass
permission_required = 'courses.add_course'
class CourseModuleUpdateView(TemplateResponseMixin, View):
template_name = 'manage/module/formset.html'
course = None
def get_formset(self, data=None,):
return ModuleFormSet(instance=self.course,data=data,)
def get_form(self, data=None):
return RequirementFormset(instance=self.course,data=data)
def get_forms(self, data=None):
return WhatYouWillLearnFormset(instance=self.course,data=data)
def dispatch(self, request, pk):
self.course = get_object_or_404(Course,id=pk,owner=request.user)
return super(CourseModuleUpdateView, self).dispatch(request, pk)
def get(self, request, *args, **kwargs):
formset = self.get_formset()
form = self.get_form()
forms = self.get_forms()
return self.render_to_response({'course':self.course,
'formset':formset,'form':form,'forms':forms,})
def post(self, request, *args, **kwargs):
formset = self.get_formset(data=request.POST,)
form = self.get_form(data=request.POST,)
forms = self.get_forms(data=request.POST,)
if formset.is_valid():
formset.save()
if form.is_valid():
form.save()
if forms.is_valid():
forms.save()
return redirect('courses:manage_course_list')
return self.render_to_response({'course': self.course,
'formset':formset,})
Here is my model, am using formset to create course and also
class Course(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL,\
related_name='courses_created', on_delete=models.CASCADE)
subject = models.ForeignKey(Subject,related_name='courses', on_delete=models.CASCADE)
title = models.CharField(max_length=200)
pic = models.ImageField(upload_to="course_pictures", blank=True,null=True)
slug = models.SlugField(max_length=200, unique=True,blank=True)
overview = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
students = models.ManyToManyField(settings.AUTH_USER_MODEL,related_name='courses_joined',blank=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
Here is my formset
class CourseForm(forms.ModelForm):
class Meta:
model = Course
fields = ['pic']
ModuleFormSet = inlineformset_factory(Course,
Module,
fields=['title','description',],
extra=2,
can_delete=True)
RequirementFormset = inlineformset_factory(Course,
Requirement,
fields=['requirements'],
extra=4,can_delete=True)
WhatYouWillLearnFormset = inlineformset_factory(Course,
WhatYouWillLearn,
fields=['hightlights'],
extra=4,can_delete=True)
I think the attribute enctype="multipart/form-data" is missing in the form tag in the template file.
<form method="post" enctype="multipart/form-data">
...
...
</form>

How do I bypass asking end-user for username while adding a blog post using a form and automatically display logged in user as post author?

I am working on a simple blog which has a model Post. I am trying to create a form for adding blog posts (or adding comments to posts for that matter) so that end users don't have to fill out a form box asking the end user for a username. I would like to be able to just ask for a title and body text for a blog post, and when hit post, it will be posted as the authenticated user.
I tried not including 'user' field in fields in forms, but it seems to be mandatory. Maybe I need to just make it hidden somehow using widgets? In templates, I could maybe write the following:
{% if user.is_authenticated %}
<p>Posting as {{request.user}}</p>
{% else %}
<p><a href={% url 'register' %}Please register to add a blog post</a></p>
{% endif %}
Though I am not sure, I think it would make more sense to have logic in my views.py file.
Here's my 'blog.models' file:
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
text = models.TextField()
published_date = models.DateTimeField(auto_now=True)
# pip install Pillow
image = models.ImageField(null=True, blank=True,
upload_to='photos/%Y/%m/%d/',)
def summary(self):
"""Return a summary for very long posts to
get a glimpse from admin panel"""
return self.text[:100]
def _get_unique_slug(self):
"""Assigns a number to the end of a given slug field to prevent
duplicated slug error. if title of a post is 'ayancik', and another
user creates another post with the same title, second posts' slug
is assigned a value: 'ayancik-2'"""
slug = slugify(self.title)
unique_slug = slug
num = 1
while Post.objects.filter(slug=unique_slug).exists():
unique_slug = '{}-{}'.format(slug, num)
num += 1
return unique_slug
def save(self, *args, **kwargs):
"""Automatically assign slug to objects
by overriding save method"""
self.slug = self._get_unique_slug()
super().save(*args, **kwargs)
def pub_date_pretty(self):
return self.published_date.strftime('%b %e, %Y')
def __str__(self):
"""String representation"""
return self.title
def get_absolute_url(self):
# what does kwargs={'slug':self.slug} really achieve here?
# where would we use 'key-value' pair?
"""Returns the url to access a detailed post"""
return reverse('post-detail', kwargs={"slug": self.slug})
class Meta:
ordering = ['-published_date',]
class Comment(models.Model):
post = models.ForeignKey('blog.Post', on_delete=models.CASCADE,
related_name='comments')
user = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve_comment(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
'blog.forms' file:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['user', 'title', 'text', 'image']
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('user', 'text',)
and 'blog.views' file:
#login_required
def create_post(request):
if request.method == 'POST':
post_form = PostForm(request.POST)
if post_form.is_valid():
post = post_form.save(request)
post.save()
else:
print(post_form.errors)
else:
# when not POST request, display the empty form
# meaning -> if request.method=='GET':
post_form = PostForm()
context = {
'post_form': post_form,
}
return render(request, 'blog/addpost.html', context)
def add_comment_to_post(request, slug):
post = get_object_or_404(Post, slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post-detail', slug=slug)
else:
form = CommentForm()
template_name = 'blog/add_comment_to_post.html'
return render(request, template_name , {'form': form })
You omit the user in the PostForm:
class PostForm(forms.ModelForm):
class Meta:
model = Post
# no user
fields = ['title', 'text', 'image']
or we can display all fields except 'user' like:
class PostForm(forms.ModelForm):
class Meta:
model = Post
exclude = ('user', )
and then you add the user to the instance in your view:
from django.shortcuts import redirect
#login_required
def create_post(request):
if request.method == 'POST':
post_form = PostForm(request.POST, request.FILES)
if post_form.is_valid():
post = post_form.save(commit=False)
post.user = request.user
post.save()
return redirect('some_view')
else:
post_form = PostForm()
context = {
'post_form': post_form,
}
return render(request, 'blog/addpost.html', context)
The commit=False thus prevents the from from saving the Post object to the database.
Since you want to upload an image, you should pass request.FILES [Django-doc] to the PostForm as well, otherwise you will not process uploaded files. You furthermore need to specify that you use enctype="multipart/form-data" in your form:
<form enctype="multipart/form-data" method="POST" action="{% url 'create_post' %}">
...
</form>
It is better to use a redirect [Django-doc] in case of a successful POST request, since this is the Post/Redirect/Get pattern [wiki]. By rendering a new form, if the user refreshes the page, he/she would create a second post, which is probably not what you want.

ManyToMany does not save to db Django

I have a ManytoMany field and the results of the CheckboxSelectMultiple don't get saved in the db and i don't understand why. It must be really simple but...
Here's the code:
models.py
class Person(models.Model):
last_name = models.CharField(max_length = 50)
first_name = models.CharField(max_length = 50)
def __str__(self):
return self.last_name +" "+self.first_name
class Event(models.Model):
owner = models.ForeignKey(User, on_delete=models.SET_NULL, null = True)
person = models.ManyToManyField(Person)
forms.py
class EventForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.get('user',None)
#self.user = kwargs.pop('user',None)
super(EventForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'POST'
#self.helper.form_action = reverse_lazy('simpleuser')
self.helper.add_input(Submit('submit', 'Submit', css_class='btn-success'))
class Meta:
model = Event
fields = ['person']
widgets ={
'person': forms.CheckboxSelectMultiple,
}
views.py
def uploadevent(request):
if request.method == "POST":
form =EventForm(request.POST)
if form.is_valid():
event = form.save(commit=False)
event.owner = request.user
event.save()
else:
form = EventForm()
return render...
See the response on this stackoverflow question: Saving Many To Many data via a modelform in Django
Quoting the OP:
When using commit=False, you have to call save_m2m()
m2m relationships require the parent object to be saved first, which
you are not doing by using commit=False
Just add this line below event.save()
if form.is_valid():
event = form.save(commit=False)
event.owner = request.user
event.save()
form.save_m2m()
Ref: https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#the-save-method
To save manytomany you should call save_m2m() when using commit=False (check details here):
if form.is_valid():
event = form.save(commit=False)
event.owner = request.user
event.save()
form.save_m2m()

Issues Saving Django ModelForms with m2m Relations

I'm trying to use Django ModelForms to allow teachers to schedule weekly classes with students. However, whenever I save a new instance of the weekly class from the modelform the m2m relations (students and teachers) are not saved.
I've looked pretty extensively in the Django documentation and tried everything to remedy this including setting commit=False on the save method and then using the save_m2m method. No dice.
Here's my code
models.py
class WeeklyClass(models.Model):
status = models.CharField(
max_length=1,
choices=STATUS_CHOICES,
default="A")
students = models.ManyToManyField(
Profile,
limit_choices_to=models.Q(is_teacher=False),
related_name='student_weekly_classes',)
teachers = models.ManyToManyField(
Profile,
limit_choices_to=models.Q(is_teacher=True),
related_name='teacher_weekly_classes',)
class Meta:
verbose_name = 'Class'
verbose_name_plural = 'Classes'
ordering = ["-created"]
forms.py
class WeeklyClassForm(ModelForm):
class Meta:
model = WeeklyClass
fields = [
"status",
"students",
"teachers",
"weekday",
"duration_hours",
"hour",
"minute"]
views.py
#login_required
def new_weekly_class(request):
if request.method == "POST":
form = WeeklyClassForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse(
"weekly_class_list",
kwargs={"username": request.user.username}))
else:
form = WeeklyClassForm()
return render(
request,
"weekly_classes/new_weekly_class.html",
{"form": form})
form.save() does not directly save the many to many instances so after saving the form.save() also call form.save_m2m() to save manytomay relationship.
#login_required
def new_weekly_class(request):
if request.method == "POST":
form = WeeklyClassForm(request.POST, request.FILES)
if form.is_valid():
form.save()
form.save_m2m()
return redirect("weekly_class_list", kwargs={"username": request.user.username})
else:
form = WeeklyClassForm()
template = "weekly_classes/new_weekly_class.html"
context = {"form": form}
return render(request, template, context)
for reference: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method

Setting user_id when saving in view - Django

I need to set the user that is creating the post in the add view:
#login_required
def add(request):
if request.method == 'POST':
form = BusinessForm(request.POST)
form.user_id = request.user.id
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('listing.views.detail'), args=(f.id))
else:
form = BusinessForm()
return render_to_response('business/add.html', {'form':form},
context_instance=RequestContext(request))
I assign the user id form.user_id = request.user.id but when saving, it still gives me an error Column user_id cannot be null
Am I doing something wrong? Thanks
EDIT:
I am excluding the user from the form in the model:
class BusinessForm(ModelForm):
class Meta:
model = Business
exclude = ('user',)
Could that be causing the problem?? How can I work around this?
EDIT 2:
Edited my BusinessForm() class as suggested but did not work:
class BusinessForm(ModelForm):
class Meta:
model = Business
exclude = ('user',)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(BusinessForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(BusinessForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
Business model
class Business(models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User, unique=False)
description = models.TextField()
category = models.ForeignKey(Category)
address = models.CharField(max_length=200)
phone_number = models.CharField(max_length=10)
website = models.URLField()
image = models.ImageField(upload_to='business_pictures',blank=True)
You don't have to use init or save overrides for this.
You're just setting an attribute on your form and the form doesn't do anything with it. It doesn't magically behave like a model instance (your form wouldn't have a user_id attribute).
Since your form is a ModelForm, you can simply call save on it with commit=False to get the unsaved instance, set the user, then call save on the instance.
if request.method == 'POST':
form = BusinessForm(request.POST)
if form.is_valid():
business = form.save(commit=False)
business.user = request.user
business.save()
this seems to be exactly what your looking for.