ModelForms and ForeignKeys - django

I got the following models:
class Project(models.Model):
name = models.CharField(max_length=50)
class ProjectParticipation(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project)
class Receipt(models.Model):
project_participation = models.ForeignKey(ProjectParticipation)
Furthermore I have the following CreateView:
class ReceiptCreateView(LoginRequiredMixin, CreateView):
form_class = ReceiptForm
model = Receipt
action = 'created'
I now want a dropdown menu where the User can choose the project, the new receipt should be for. The user should only see the project he is assigned to.
How can I do that?

The simply answer is just create a model form read the docs, this is fundamental.
You may also want to look at related names, that way you can get the reverse on the FK.
class ProjectParticipation(models.Model):
user = models.ForeignKey(User)
project = models.ForeignKey(Project, related_name='ProjectParticipation')

I found a solution using a ModelChoiceField:
class ProjectModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.project
class ReceiptForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ReceiptForm, self).__init__(*args, **kwargs)
self.fields['project_participation'] = ProjectModelChoiceField(queryset= ProjectParticipation.objects)
class Meta:
model = Receipt
And then in the CreateView:
class ReceiptCreateView(...)
def get_form(self, form_class):
form = super(ReceiptCreateView, self).get_form(form_class)
form.fields['project_participation'].queryset = ProjectParticipation.objects.filter(user=self.request.user)
return form
Is there a solution to filter the query set directly in the ModelForm?

Related

Auto create related model

I am wondering if it's possible to auto create a related model upon creation of the first model.
This is the models
class Team(models.Model):
name = models.CharField(max_length=55)
class TeamMember(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE, null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=False)
So what I want to do is something like this on the 'Team' model
class Team(models.Model):
name = models.CharField(max_length=55)
#on_new.do_this
TeamMember.team = self
TeamMember.user = request.user
TeamMember.save()
I have tried to find any documentation about this. But only found some example about onetoonefields. But nothing about this.
Appreciate any help. Cheers!
I am assuming you are using forms to create team.
There is no direct way to create the TeamMember instance without the current user(via request). request is available in views only(unless you are using special middleware or third party library to access it), so we can send it form and create the user by overriding the save method of the modelform.
So you can try like this:
# Override the model form's save method to create related object
class TeamForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(TeamForm, self).__init__(*args, **kwargs)
class Meta:
model = Team
def save(self, **kwargs):
user = self.request.user
instance = super(TeamForm, self).save(**kwargs)
TeamUser.objects.create(team=instance, user=user)
return instance
And use this form in View:
# Update get_form_kwargs methods in create view
class TeamCreateView(CreateView):
form_class = TeamForm
template = 'your_template.html'
def get_form_kwargs(self):
kw = super(TeamCreateView, self).get_form_kwargs()
kw['request'] = self.request
return kw
Update
(from comments)If you have the user FK availble in Team then you can use it to create TeamMember by overriding the save method. Try like this:
class Team(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL)
name = models.CharField(max_length=55)
def save(self, *args, **kwargs): # <-- Override
instance = super(Team, self).save(*args, **kwargs)
TeamMember.objects.create(user=instance.user, team=instance)
return instance

Django Generic Model Formsets

So I have a site where users are capable of registering devices and then registering commands to those devices. These commands have an associated widget (button, slider, etc) that determine the unique properties that the command has.
I am trying to figure out the most generic way to use the model formsets in my application. I have things working, where I create a model_formset for each ModelForm, and get data from each model to place in the formset, and then show it in my template.
What I would really like to do is something like this:
command_formset = modelformset_factory(Command, extra=0, fields='__all__')
formset = command_formset(queryset=Command.objects.filter(device_id=device_id))
Where I get all of my commands (which returns both buttons and sliders) and then make a single command_formset, which has all command objects.
In this case it does part of what I am looking for, where I can query my model and get all button and slider commands, but the formset only includes the fields from command - so the min and max value for the slider are lost.
Is something like this possible? Here is my more complete code:
Models
class Command(models.Model):
command_name = models.CharField(max_length=100)
command = models.CharField(max_length=100)
command_url = models.CharField(max_length=100)
command_type = models.CharField(max_length=100)
device = models.ForeignKey(Device, on_delete=models.CASCADE, default=1) # Each command has one device
def __str__(self):
return self.command_name
class ButtonCommand(Command):
button_color = models.CharField()
class SliderCommand(Command):
min_value = models.IntegerField()
max_value = models.IntegerField()
Forms
class CommandForm(ModelForm):
class Meta:
model = Command
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({'class': 'form-control'})
class ButtonCommandForm(ModelForm):
class Meta:
model = ButtonCommand
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({'class': 'form-control'})
class SliderCommandForm(ModelForm):
class Meta:
model = SliderCommand
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({'class': 'form-control'})
View
command_formset = modelformset_factory(Command, extra=0, fields='__all__')
button_command_formset = modelformset_factory(ButtonCommand, form=ButtonCommandForm, extra=0,
fields=('command_name', 'command', 'command_type', 'command_url'))
slider_command_formset = modelformset_factory(SliderCommand, extra=0,
fields=('command_name', 'command', 'command_type', 'command_url',
'min_value', 'max_value'))
device_form = DeviceForm(initial=device)
formset = command_formset(queryset=Command.objects.filter(device_id=device_id))
button_formset = button_command_formset(queryset=ButtonCommand.objects.filter(device_id=device_id),
prefix="button_form")
slider_formset = slider_command_formset(queryset=SliderCommand.objects.filter(device_id=device_id),
prefix="slider_form")
Edit Additional View Code
template = 'device_control/device_management.html'
return TemplateResponse(request, template, {'device': device,
'device_form': device_form,
'button_formset': button_formset,
'slider_formset': slider_formset,
'formset' : formset})
Another Related Question
input_type = request.POST['command_type']
if input_type == 'button':
form = ButtonCommandForm(request.POST)
elif input_type == 'slider':
form = SliderCommandForm(request.POST)
else:
form = None
Hopefully I am not overwhelming, but the above is a very similar question that seems much simpler. User posts a form, both of which inherit from Command. While the above is quite simple, if I eventually have 20+ different types of CommandForms, this will get fairly nasty.
I am really hoping that I am missing some way that Django can tell which child form should be used to build the form.

Limit the Choices shown from ManyToMany ForeignKey

How do I limit the values returned via the ManyToMany relationship and thus displayed in the <SELECT> field on my form to only show the spots which were created by the currently logged in user?
models.py
class Project(models.Model):
owner = models.ForeignKey(User, editable=False)
...
spots = models.ManyToManyField(to='Spot', blank=True, )
class Spot(models.Model):
owner = models.ForeignKey(User, editable=False)
spot_name = models.CharField(max_length=80, blank=False)
forms.py
from django import forms
from .models import Project, Spot
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', )
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
I'm using GenericViews for Update and Create and currently see all of the entries everyone has made into Spots when I'm updating or creating a Project. I want to see only the entries entered by the logged in user. For completeness sake, yes, the project.owner and spot.owner were set to User when they were created.
I've tried def INIT in the forms.py and using limit_choices_to on the manytomany field in the model. Either I did those both wrong or that's not the right way to do it.
thank you!
in your forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', )
def __init__(self, user_id, *args, **kwargs):
self.fields['spots'] = forms.ModelChoiceField(widget=forms.Select, queryset=Project.objects.filter(owner=user_id))
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
def __init__(self, user_id, *args, **kwargs):
self.fields['spot_name'] = forms.ModelChoiceField(widget=forms.Select, queryset=Spot.objects.filter(owner=user_id))
in your views.py
user_id = Project.objects.get(owner=request.user).owner
project_form = ProjectForm(user_id)
spot_form = SpotForm(user_id)
As I mentioned above, Dean's answer was really close, but didn't work for me. Primarily because request is not accessible in the view directly. Maybe it is in older Django versions? I'm on 1.9. Thank you Dean, you got me over the hump!
The gist of what's going on is adding User into the kwargs in the View, passing that to the ModelForm, remove User from the kwargs and use it to filter the Spots before the form is shown.
This is the code that worked for my project:
views.py
class ProjectUpdate(UpdateView):
model = Project
success_url = reverse_lazy('projects-mine')
form_class = ProjectForm
def dispatch(self, *args, **kwargs):
return super(ProjectUpdate, self).dispatch(*args, **kwargs)
def get_form_kwargs(self):
kwargs = super(ProjectUpdate, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', 'whispir_id')
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user')
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['spots'] = forms.ModelMultipleChoiceField(queryset=Spot.objects.filter(owner=user_id))
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user')
super(SpotForm, self).__init__(*args, **kwargs)
self.fields['spot_name'] = forms.ModelMultipleChoiceField(queryset=Spot.objects.filter(owner=user_id))

add foreign key fields to form

I'm trying to create a form from this model:
class A(models.Model):
u = models.OneToOneField(User)
and then create this form:
class AForm(ModelForm):
class Meta:
model = A
fields = ['u']
then i create an instance of that form in my view and send it to my template as a context I'll get a drop down list to choose from existing users but what i want to do is to have a text field to change my current user's first name or last name.
I'll be grateful if you could help me to change my form class to get the right result.
Thank you
You can add the first and last name fields to the AForm ModelForm in the following way:
class AForm(ModelForm):
first_name = forms.CharField(max_length=30, blank=True)
last_name = forms.CharField(max_length=30, blank=True)
class Meta:
Model = A
def __init__(self, *args, **kwargs):
super(AForm, self).__init__(*args, **kwargs)
self.fields['first_name'].initial = self.instance.u.first_name
self.fields['last_name'].initial = self.instance.u.last_name
def save(self, commit=True):
self.instance.u.first_name = self.cleaned_data['first_name']
self.instance.u.last_name = self.cleaned_data['last_name']
self.instance.u.save()
return super(AForm, self).save(commit=commit)
In this case you do not need a modelform of A but a modelform of User. You would need to set the form's instance appropriately in the view. For example,
a_record = A.objects.get_object_or_404(A, id=1)
form = self.UserForm(instance=a.u) # UserForm is a modelform of User

How do I add a Foreign Key Field to a ModelForm in Django?

What I would like to do is to display a single form that lets the user:
Enter a document title (from Document model)
Select one of their user_defined_code choices from a drop down list (populated by the UserDefinedCode model)
Type in a unique_code (stored in the Code model)
I'm not sure how to go about displaying the fields for the foreign key relationships in a form. I know in a view you can use document.code_set (for example) to access the related objects for the current document object, but I'm not sure how to apply this to a ModelForm.
My model:
class UserDefinedCode(models.Model):
name = models.CharField(max_length=8)
owner = models.ForeignKey(User)
class Code(models.Model):
user_defined_code = models.ForeignKey(UserDefinedCode)
unique_code = models.CharField(max_length=15)
class Document(models.Model):
title = models.CharField(blank=True, null=True, max_length=200)
code = models.ForeignKey(Code)
active = models.BooleanField(default=True)
My ModelForm
class DocumentForm(ModelForm):
class Meta:
model = Document
In regards to displaying a foreign key field in a form you can use the forms.ModelChoiceField and pass it a queryset.
so, forms.py:
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
def __init__(self, *args, **kwargs):
user = kwargs.pop('user','')
super(DocumentForm, self).__init__(*args, **kwargs)
self.fields['user_defined_code']=forms.ModelChoiceField(queryset=UserDefinedCode.objects.filter(owner=user))
views.py:
def someview(request):
if request.method=='post':
form=DocumentForm(request.POST, user=request.user)
if form.is_valid():
selected_user_defined_code = form.cleaned_data.get('user_defined_code')
#do stuff here
else:
form=DocumentForm(user=request.user)
context = { 'form':form, }
return render_to_response('sometemplate.html', context,
context_instance=RequestContext(request))
from your question:
I know in a view you can use
document.code_set (for example) to
access the related objects for the
current document object, but I'm not
sure how to apply this to a ModelForm.
Actually, your Document objects wouldn't have a .code_set since the FK relationship is defined in your documents model. It is defining a many to one relationship to Code, which means there can be many Document objects per Code object, not the other way around. Your Code objects would have a .document_set. What you can do from the document object is access which Code it is related to using document.code.
edit: I think this will do what you are looking for. (untested)
forms.py:
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
exclude = ('code',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user','')
super(DocumentForm, self).__init__(*args, **kwargs)
self.fields['user_defined_code']=forms.ModelChoiceField(queryset=UserDefinedCode.objects.filter(owner=user))
self.fields['unique_code']=forms.CharField(max_length=15)
views.py:
def someview(request):
if request.method=='post':
form=DocumentForm(request.POST, user=request.user)
if form.is_valid():
uniquecode = form.cleaned_data.get('unique_code')
user_defined_code = form.cleaned_data.get('user_defined_code')
doc_code = Code(user_defined_code=user_defined_code, code=uniquecode)
doc_code.save()
doc = form.save(commit=False)
doc.code = doc_code
doc.save()
return HttpResponse('success')
else:
form=DocumentForm(user=request.user)
context = { 'form':form, }
return render_to_response('sometemplate.html', context,
context_instance=RequestContext(request))
actually you probably want to use get_or_create when creating your Code object instead of this.
doc_code = Code(user_defined_code=user_defined_code, code=uniquecode)