Auto create related model - django

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

Related

How to filter queryset value in one of the form fields using CreateView?

My task is to change the value of one field in the form (drop-down list with Foreignkey connection). I need to exclude the values of technology that the user already has.
I use CreateView and ModelForm.
forms.py
class SkillCreateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SkillCreateForm, self).__init__(*args, **kwargs)
employee_current_technology = Technology.objects.filter(??? --- How can I get editing user pk ????-----)
self.fields['technology'].queryset = Technology.objects.exclude(name__in=employee_current_technology)
I know that somehow I can get pk from url using kwarg and get_form_kwarg values, but I can't figure out how to do that.
urls.py
path('profile/<int:pk>/skill/create/', SkillCreateView.as_view(), name='skill_create'),
views.py
class SkillCreateView(AuthorizedMixin, CreateView):
"""
Create new course instances
"""
model = Skill
form_class = SkillCreateForm
template_name = 'employee_info_create.html'
def get_form_kwargs(self):
kwargs = super(SkillCreateView, self).get_form_kwargs()
Employee.objects.get(pk=self.kwargs['pk']) -->get me pk
????
return kwargs
.....
models.py
class Employee(models.Model):
"""Employee information."""
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='employee')
summary = models.TextField("summary", blank=True, default='')
skills = models.ManyToManyField(
Technology, through="Skill", verbose_name="skills", blank=True)
class Skill(models.Model):
"""Information about an employee's skills."""
employee = models.ForeignKey(
Employee, on_delete=models.CASCADE, related_name="employee_skills")
technology = models.ForeignKey(Technology, on_delete=models.CASCADE)
class Technology(models.Model):
"""Technologies."""
tech_set = models.ForeignKey(Skillset, on_delete=models.CASCADE, related_name="skillset")
name = models.CharField('technology name', max_length=32, unique=True)
group = models.ForeignKey(Techgroup, on_delete=models.CASCADE, related_name="group")
You can inject the pk in the form, like:
class SkillCreateView(AuthorizedMixin, CreateView):
"""
Create new course instances
"""
model = Skill
form_class = SkillCreateForm
template_name = 'employee_info_create.html'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update(employee_pk=self.kwargs['pk'])
return kwargs
You can then update the queryset in the form like:
class SkillCreateForm(forms.ModelForm):
def __init__(self, *args, employee_pk=None, **kwargs):
super().__init__(*args, **kwargs)
if employee_pk is not None:
self.fields['technology'].queryset = Technology.objects.exclude(
skill__employee_id=employee_pk
)

Django Use Many To Many with Through in Form

I have a data model where I am using a manual intermediate table for a m2m relationship.
Building on the classical example from the django doc:
from django.db import models
INSTRUMENT_CHOICES = (
('guitar', 'Guitar'),
('bass', 'Bass Guitar'),
('drum', 'Drum'),
('keyboard', 'Keyboard'),
)
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Leadership')
def __str__(self):
return self.name
def get_leadership():
return self.leadership_set.first()
class Leadership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
instrument = models.CharField('Playing Instrument', choices=INSTRUMENT_CHOICES,
max_length=15,
null=True,
blank=False)
class Meta:
unique_together = ('person', 'group')
When I create a new group I also want to specify who is going to be the leader, and for this relationship also specify which instrument he will play in that group.
What really confuses me, given also the lack of documentation on this topic is how to handle this kind of relationship in forms.
This is the form I came with:
class InstrumentField(forms.ChoiceField):
def __init__(self, *args, **kwargs):
super().__init__(INSTRUMENT_CHOICES, *args, **kwargs)
class GroupForm(forms.ModelForm):
instrument = InstrumentField(required=False)
class Meta:
model = Group
fields = ['name',
'members'
'instrument'] # This works but it's not correctly initalized in case of edit form
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk is not None: # editing
# PROBLEM: this doesn't work
self.fields["instrument"].initial = self.instance.get_leadership().instrument
def save(self, commit=True):
group = super().save(commit=False)
if commit:
group.save()
if 'instrument' in self.changed_data:
leader = self.cleaned_data.get('members').first()
instrument = self.cleaned_data['instrument']
Leadership.objects.update_or_create(person=leader, group=group, defaults={'instrument': instrument})
return group
As suggested in the django doc I am manually instantiating Leadership objects (see the form save method).
What I couldn't solve is how to populate the instrument field in case of form editing. I try to do this in the __init__: first I check that we are in "edit" mode (the instance has a pk) then I get the relevant Leadership object (see Group.get_leadership) and from that I extract the instrument and I assign it to the fields["instrument"].initial.
This doesn't work.
I could inspect that the initial value was set but then when I render the form the default choice value is shown (the first value of the INSTRUMENT_CHOICES).
What am I missing here?
Is there a better way or a better docs on how to handle m2m with through model in forms?

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))

ModelForms and ForeignKeys

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?

How can I filter objects when building a form from a model in Django?

class TriggerForm(ModelForm):
class Meta:
model = Trigger
class Trigger(models.Model):
account = models.ForeignKey(Account)
name = models.CharField(max_length=255)
schedule = models.ForeignKey(Schedule)
def __unicode__(self):
return self.name
When the trigger form is shown I only want to show schedule items that are for the logged in account but instead it shows me all schedule objects.
You can pass in the request as an argument to the TriggerForm and then use that to filter the Schedule...
class TriggerForm(ModelForm):
class Meta:
model = Trigger
def __init__(self, request=None, *args, **kwargs):
super(TriggerForm, self).__init__(*args, **kwargs)
self.fields['schedule'] = forms.ModelChoiceField(queryset=Schedule.objects.filter([your filter here based on the logged in user]))
#views.py
from my_app.forms import TriggerForm
def my_view(request):
form = TriggerForm(request, request.POST or None)
# and so on
Hope that helps you out.