It all works according to that logic. There is a Project model that contains general information about the project. Many users can work on this project (model Employee) and perform their part of the project work (model Work).
I created a view for edditing forms using inlineformset_factory.
forms.py
ProjectEditFormSet = inlineformset_factory(Project, Work,
form=WorkCreateForm,
extra=0)
models.py
class Work(models.Model):
"""On which projects an employee has worked."""
employee = models.ForeignKey(
Employee, on_delete=models.CASCADE, related_name='employee_projects')
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='project_work')
start_year = models.CharField('work start year', max_length=4, default=datetime.datetime.now().year)
start_month = models.CharField('work start month', max_length=10, default=datetime.datetime.now().month)
end_year = models.CharField('work end year', max_length=4, default=datetime.datetime.now().year)
end_month = models.CharField('work end month', max_length=10, default=datetime.datetime.now().year)
responsibility = models.TextField("employee work responsibility", blank=True)
technologies = models.ManyToManyField(
Technology, verbose_name="technologies used on the project")
class Project(models.Model):
"""Project information."""
name = models.CharField("project name", max_length=64)
description = models.TextField("project description")
views.py
class WorkEditView(AuthorizedMixin, UpdateView):
"""
Edit new project instances
"""
model = Project
form_class = ProjectForm
template_name = 'work_edit.html'
def get_context_data(self, **kwargs):
context = super(WorkEditView, self).get_context_data(**kwargs)
context['project_form'] = ProjectEditFormSet(instance=self.object)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
project_form = WorkFormSet(self.request.POST, instance=self.object)
if form.is_valid() and project_form.is_valid():
return self.form_valid(form, project_form)
else:
return self.form_invalid(form, project_form)
def form_valid(self, form, project_form):
self.object = form.save()
project_form.instance = self.object
project_form.save()
return redirect('{}#experience'.format(reverse('profile', kwargs={'pk': self.kwargs['employee']})))
How it works for me now.
When editing, displayed all the Work forms related to the project (form for each user involved in the project)
What do I want to get as a result
Only the editing form belonging to the current user, whose profile is being edited, is displayed.
urls.py
path('work/<int:employee>/<int:pk>/edit/', WorkEditView.as_view(), name='work_info_edit'),
where <int:employee> - current employee; <int:pk> - Project id
I think I need to change
def get_context_data(self, **kwargs):
context = super(WorkEditView, self).get_context_data(**kwargs)
context['project_form'] = ProjectEditFormSet(instance=self.object) <--Smth here need change ??????
but I can't find a way to do it.
Like with any ModelFormSet, you can specify a queryset argument when instantiating your ProjectEditFormSet:
employee_id = self.kwargs['employee']
employee = Employee.objects.get_or_404(id=employee_id)
project_form = ProjecEditFormSet(request.POST, instance=self.object,
queryset=Work.objects.filter(employee=employee))
Related
I'm new to programming and my first language/stack is Python and Django. I have figured out how to create a dropdown menu in my Script form that is pointing to a different class "Patient" but I can't figure out how to only show me data that the current user created. I'm confused if I should set this in my models.py, forms.py or in the views.py? Here is what I have that I think should be working but it is not. (Tried setting in the views.py)
Models.py
class Patient(models.Model):
author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,)
patient_name = models.CharField(max_length=40, unique=True)
def __str__(self):
return self.patient_name
class Script(models.Model):
author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE, verbose_name='Primary Patient')
So my patient field is my dropdown and it is looking at the Patient class grabbing the patient name string. I only want patient_name entry's that this user created in the dropdown.
Views.py
class ScriptCreateView(LoginRequiredMixin, CreateView):
model = Script
template_name = 'script_new.html'
success_url = reverse_lazy('script_list')
fields = (
'patient',
'drug_name',
'drug_instructions',
'drug_start_day',
'drug_start_time',
'drug_hours_inbetween',
'drug_num_days_take',
)
#This sets user created fields only??
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
author=self.request.user
)
#This sets the author ID in the form
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form
)
Forms.py
class ScriptForm(forms.ModelForm):
class Meta:
model = Script
fields = '__all__'
#This is requiring user login for any of these views??
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if user:
self.fields['patient'].queryset = Patient.objects.filter(author=user)
I'm sure it is my lack of experience here but I thought by setting the function def get_queryset in the view that it would only show me user created data. I have googled a bunch and I really can't find the clear answer on this.
In your views.py file initialize form like this please
<form or form_class> = Form(request.POST, user=request.user)
I had to add the last form.fields query below in the view which filtered items only created by "author" which is what I was looking for:
def get_form(self):
form = super().get_form()
form.fields['drug_start_day'].widget = DatePickerInput()
form.fields['drug_start_time'].widget = TimePickerInput()
form.fields['patient'].queryset = Patient.objects.filter(author=self.request.user)
return form
I have the two models, Fillup and Car, and the Fillup model has a Foreign key (for recording times you fill up your car with gas, for example), and in the form to create a new Fillup, I want to limit the dropdown for the Car field to only Cars associated with the current user, but right now it's showing all users cars. I've seen a couple solutions that involve passing the request into the form from the view but I can't figure out how to do it using the Class Based Views I currently have set up. Here's my code:
models.py
class Fillup(models.Model):
username = models.ForeignKey(User,on_delete=models.CASCADE)
date = models.DateField(default=date.today)
price_per_gallon = models.FloatField()
trip_distance = models.FloatField()
gallons = models.FloatField()
car = models.ForeignKey('Car',on_delete=models.CASCADE)
#property
def total_sale(self):
return round(self.price_per_gallon*self.gallons, 2)
#property
def mpg(self):
return round(self.trip_distance/self.gallons, 4)
class Car(models.Model):
username = models.ForeignKey(User,on_delete=models.CASCADE)
name = models.CharField(max_length=25)
make = models.CharField(max_length=25)
model = models.CharField(max_length=25)
model_year = models.IntegerField(choices=MODEL_YEARS)
status = models.BooleanField(choices=STATUS)
def __str__(self):
return self.name
views.py
class FillupListView(ListView):
model = Fillup
context_object_name = 'fillup_list'
ordering = ['-date']
# NOT USING THIS YET
# def get_queryset(self):
# return Fillup.objects.filter(user=self.request.user)
class CarListView(ListView):
model = Car
ordering = ['name']
class NewFillup(LoginRequiredMixin,CreateView):
model = Fillup
fields = ('date', 'price_per_gallon', 'trip_distance', 'gallons', 'car')
redirect_field_name = 'fillup_list'
def form_valid(self, form):
form.instance.username = self.request.user
return super().form_valid(form)
class NewCar(LoginRequiredMixin,CreateView):
model = Car
fields = ('name', 'make', 'model', 'model_year', 'status')
redirect_field_name = 'car_list'
def form_valid(self, form):
form.instance.username = self.request.user
return super().form_valid(form)
forms.py
class FillupForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(FillupForm,self).__init__(*args, **kwargs)
self.fields['car'].queryset = Car.objects.filter(username=user)
class Meta():
model = Fillup
fields = ('date', 'price_per_gallon', 'trip_distance', 'gallons', 'car')
class CarForm(forms.ModelForm):
class Meta():
model = Car
fields = ('name', 'make', 'model', 'model_year', 'status')
The overwriting of the init method in FillupForm was just one of the things I tried to get this to work, adapted from another Stackoverflow answer, but it didn't seem to have any effect. Any advice/examples to get this working would be appreciated! And let me know if I should supply any more pieces of my code
I ended up getting my answer to this from r/djangolearning on Reddit.
I needed to add the following to both of my CreateViews:
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
They also pointed out that I needed to replace the fields=('blah blah blah') on both CreateViews with form_class=forms.Fillup/Car
I hope this helps someone with the same issue as me!
You can do something like this in the init method.
cars = Car.objects.filter(username=user)
self.fields['car'].autocomplete = False
self.fields['car'].queryset = users
Hope this helps.
I have a comment section in django blog and there are two forms one is for comment another is for reply to the comment but the comment form is working fine and reply form doesn't work! i was trying to do but getting error... IntegrityError at /page/9/
FOREIGN KEY constraint failed...
appreciate to your help :)
Thank you.
views.py
class PostDetailView(DetailView):
model = Post
template_name = "post_detail.html"
context_object_name = 'post'
form = CommentForm()
def get_object(self):
obj = super().get_object()
if self.request.user.is_authenticated:
PostView.objects.get_or_create(
user=self.request.user,
post=obj
)
return obj
def get_context_data(self, **kwargs):
category_count = get_category_count()
most_recent = Post.objects.order_by('-timestamp')[:3]
context = super().get_context_data(**kwargs)
context['most_recent'] = most_recent
context['page_request_var'] = "page"
context['category_count'] = category_count
context['form'] = self.form
return context
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
form = ReplyForm(request.POST)# how to work with this form like above from
if form.is_valid():
post = self.get_object()
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect(reverse("post-detail", kwargs={
'pk': post.pk
}))
models.py
class Reply(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
content = models.TextField()
comment = models.ForeignKey('Comment', related_name='replies',default=False, null=True,
on_delete=models.CASCADE)
def __str__(self):
return self.content
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
content = models.TextField()
post = models.ForeignKey('Post', related_name='comments', default=False,
on_delete=models.CASCADE)
def __str__(self):
return self.content
You might find it easier if you did not struggle with trying to persuade a Class-based view to do what it was not intended to do, and instead used a plain old Function-based view.
Here is a two-form view. The code has been refactored into what I regard as a better pattern, to validate both forms and redisplay if anything is wrong at the top, and then you just do the actual work to create and save the objects at the bottom.
def receive_uncoated( request): #Function based view
# let's put form instantiation in one place not two, and reverse the usual test. This
# makes for a much nicer layout with actions not sandwiched by "boilerplate"
# note any([ ]) forces invocation of both .is_valid() methods
# so errors in second form get shown even in presence of errors in first
args = [request.POST, ] if request.method == "POST" else []
batchform = CreateUncWaferBatchForm( *args )
po_form = CreateUncWaferPOForm( *args, prefix='po')
if request.method != "POST" or any(
[ not batchform.is_valid(), not po_form.is_valid() ]):
return render(request, 'wafers/receive_uncoated.html', # can get this out of the way at the top
{'batchform': batchform,
'po_form': po_form,
})
#POST, everything is valid, do the work
# create and save some objects based on the validated forms ...
return redirect( 'wafers:ok' )
I am trying to use inlineformset_factory to create instances of the same model.
models.py
class Skill(models.Model):
employee = models.ForeignKey(
Employee, on_delete=models.CASCADE, related_name="employee_skills")
technology = models.ForeignKey(Technology, on_delete=models.CASCADE)
year = models.CharField('common year using amount ', max_length=4)
last_year = models.CharField('Last year of technology using ', max_length=4)
level = models.CharField("experience level", max_length=64, choices=LEVELS)
class Techgroup(models.Model):
""" Group of technology """
name = models.CharField('group_name', max_length=32, unique=True)
class Technology(models.Model):
"""Technologies."""
name = models.CharField('technology name', max_length=32, unique=True)
group = models.ForeignKey(Techgroup, on_delete=models.CASCADE, related_name="group")
In the Administrator pane I created 2 instances of the Techgroup model:
- Framework
- Programming language
All Skill models belong to one of two groups. On the front I display 2 forms, one containing queryset with instances belonging to the Framework, the other with instances belonging to the Programming language.
I divide Querysets using ModelsForm:
forms.py
class SkillBaseCreateForm(forms.ModelForm):
YEAR_CHOICES = [(r, r) for r in range(1, 11)]
LAST_YEAR_CHOICES = [(r, r) for r in range(2015, datetime.datetime.now().year + 1)]
year = forms.CharField(
widget=forms.Select(choices=YEAR_CHOICES),
)
last_year = forms.CharField(widget=forms.Select(choices=LAST_YEAR_CHOICES))
class Meta:
model = Skill
fields = ['technology', 'level', 'last_year', 'year']
class SkillCreatePLanguageForm(SkillBaseCreateForm):
def __init__(self, *args, **kwargs):
super(SkillCreatePLanguageForm, self).__init__(*args, **kwargs)
self.fields['technology'].queryset = Technology.objects.filter(group__name="Programming language")
class SkillCreateFrameworkForm(SkillBaseCreateForm):
def __init__(self, *args, **kwargs):
super(SkillCreateFrameworkForm, self).__init__(*args, **kwargs)
self.fields['technology'].queryset = Technology.objects.filter(group__name="Framework")
SkillFrameworkFormSet = inlineformset_factory(Employee, Skill, form=SkillCreateFrameworkForm, extra=1, can_delete=False)
SkillPLanguageFormSet = inlineformset_factory(Employee, Skill, form=SkillCreatePLanguageForm, extra=1, can_delete=False)
views.py
class SkillTestCreateView(AuthorizedMixin, CreateView):
"""
Create new skill instances
"""
template_name = 'edit.html'
model = Employee
form_class = EmployeeEditForm
def get(self, *args, **kwargs):
"""
Handles GET requests and instantiates blank versions of the form
and its inline formsets.
"""
self.object = Employee.objects.get(pk=self.kwargs['pk'])
form_class = self.get_form_class()
form = self.get_form(form_class)
form_framework = SkillFrameworkFormSet()
form_language = SkillPLanguageFormSet()
return self.render_to_response(
self.get_context_data(form=form,
form_framework=form_framework,
form_language=form_language))
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance and its inline
formsets with the passed POST variables and then checking them for
validity.
"""
self.object = Employee.objects.get(pk=self.kwargs['pk'])
form_class = self.get_form_class()
form = self.get_form(form_class)
form_framework = SkillFrameworkFormSet(self.request.POST)
form_language = SkillPLanguageFormSet(self.request.POST)
if (form.is_valid() and form_framework.is_valid() and
form_language.is_valid()):
return self.form_valid(form, form_framework, form_language)
else:
return self.form_invalid(form, form_framework, form_language)
def form_valid(self, form, form_framework, form_language):
"""
Called if all forms are valid. Creates a Employee instance along with
associated models and then redirects to a
success page.
"""
self.object = form.save()
form_framework.instance = self.object
form_framework.save()
form_language.instance = self.object
form_language.save()
return HttpResponseRedirect(reverse_lazy('profile', args=[self.kwargs['pk']]))
def form_invalid(self, form, form_framework, form_language):
"""
Called if a form is invalid. Re-renders the context data with the
data-filled forms and errors.
"""
return self.render_to_response(
self.get_context_data(form=form,
form_framework=form_framework,
form_language=form_language,
))
The problem is that I always get an error message when I submit a form:
Select a valid choice. That choice is not one of the available choices.
The problem is that when I submit a form, I always get an error message in the technology field of the queryset which is displayed first in the template.
That is, if the template
{form_framework}}
{form_language}
a mistake on
queryset = Technology.objects.filter(group__name="Framework"
if
{form_language}
{form_framework}}
a mistake on
queryset = Technology.objects.filter(group__name="Programming language"
If I leave only one form in views.py, everything starts to work.
I've been trying to figure it out for 2 days and I think I'm at a dead end. Need help!
In the admin panel, I can add Persons to my CompleteClass model. There is a M2M relationship between CompleteClass and Person. But, my form doesn't work as it should. The pub_date will update, and I can save the head_count, but not the ModelMultipleChoiceField (persons) -- it will not save.
models.py
class Person(models.Model):
name = models.CharField(max_length=255)
persona_description = models.CharField(max_length=255)
def __str__(self):
return self.name
class CompleteClass(models.Model):
persons = models.ManyToManyField(Person)
class_name = models.CharField(max_length=255)
class_head_count = models.IntegerField()
class_pub_date = models.DateField()
def __str__(self):
return '%s %s' % (self.class_name, self.class_head_count)
def save_complete_class(self):
self.class_pub_date = timezone.now()
self.save()
class Meta:
ordering = ('class_pub_date',)
Here is views.py:
def class_new(request):
if request.method == "POST":
form = CompleteClassForm(request.POST)
if form.is_valid():
complete_class = form.save(commit=False)
complete_class.class_pub_date = timezone.now()
complete_class.save()
form.save_m2m()
return redirect('class_detail', pk=complete_class.pk)
else:
form = CompleteClassForm()
return render(request, 'app/class_edit.html', {'form': form})
and forms.py
class CompleteClassForm(forms.ModelForm):
class Meta:
model = CompleteClass
fields = ('class_name', 'class_head_count',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(CompleteClassForm, self).__init__(*args, **kwargs)
self.fields['class_persons']=forms.ModelMultipleChoiceField(queryset=Person.objects.all())
I've read through the documentation and used the save_m2m since i've set commit=false.
The POST data contains person data, but it's not being written to the database. I'm stumped. Please help!
Only fields named in the fields tuple are saved to the instance. You don't have your m2m field listed there.
You also define your modelchoicefield with a different name - class_persons instead of persons. In fact, there is no reason to define that field separately at all - you haven't changed any of the attributes from the defaults.
And once you've removed that definition, there is also no reason to override __init__, seeing as you never pass the user parameter nor do you use it anywhere in the form.