Django inlineformset_factory - django

I'm stuck on a problem with Django framework. In detail, user can create a Group and associate Member (>=1 && <=3) to the group. Each member is identified by email address (unique). However, the same member can partecipate with different name and surname in different groups. So I have a many to many relationship (using through) between Group and Member.
In my view I've an inlineformset. However when I submit the form I always get:
archi_groupmember.group_id may not be NULL
I've changed the widget for the MemberGroup form, because the user don't have to select a member from a select but he has to type the member's mail.
So I think I should persist the member just before to save the MemberGroup, but I don't know how to do it!
I'm totally new to Django, I'm following the docs.
Thank you all for any help!!
Here a page screenshot just to clarify: http://postimg.org/image/bndh5ug29/
Following my code.
Models.py:
class Group(models.Model):
user = models.ForeignKey(User)
limit = Q(active = True)
project = models.ForeignKey(Project, limit_choices_to = limit)
name = models.CharField(max_length=100)
code = models.CharField(max_length=100, unique=True)
def __unicode__(self):
return self.name
class Member(models.Model):
groups = models.ManyToManyField(Group, through='GroupMember')
email = models.EmailField(max_length=254, unique=True)
def __unicode__(self):
return self.email
class GroupMember(models.Model):
group = models.ForeignKey(Group)
member = models.ForeignKey(Member)
name = models.CharField(max_length=100)
surname = models.CharField(max_length=100)
views.py:
#login_required
#user_passes_test(first_login_check, login_url='/detail')
def partecipate(request):
from django.forms import TextInput
MemberFormSet = inlineformset_factory(Group, Member.groups.through, form=GroupMemberForm, can_delete=False, extra=3, widgets={'member': TextInput() } )
if request.method == 'POST':
form = GroupForm(request.POST)
member_set = MemberFormSet(request.POST)
if form.is_valid():
for form in member_set:
print vars( form['member'] )
group = form.save(commit=False)
group.user = request.user
group.code = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10))
group.save()
member_set = MemberFormSet(request.POST, instance=group)
member_set = member_set.save(commit=False)
payment = Payment()
payment.group = group
payment.invoice = _createInvoice(group.project.id, group.id)
payment.save()
return HttpResponseRedirect("/")
else:
return render(request, "partecipate.html", { 'form': form, 'member_set' : member_set })
else:
form = GroupForm()
form.Meta.model.project.queryset = Project.objects.filter(active=True)
member_set = MemberFormSet(initial=[ {'name': request.user.first_name,'surname': request.user.last_name,'member':request.user.email} ])
return render(request, "partecipate.html", { 'form': form, 'member_set' : member_set })

What version of Django are you using?
Since 1.3 the help docs has an example of using inlineformset_factory. And the example (link below) clearly shows passing in an instance of the parent record. Without that, your formsets will not be linked to real data.
https://docs.djangoproject.com/en/1.7/topics/forms/modelforms/#using-an-inline-formset-in-a-view
I'm also suspicious of specifying the Member.groups.through table class as the child. I would think you should leave it as Member, because the through table is part of the internal machinery.

Related

With Django, how do I reference an existing model in a form save method instead of creating a new instance?

I'm trying to use a ModelChoiceField to display options populated from model, and when a user selects a choice, store that method in a different model.
I'm using a standard form instead of a ModelForm, because I wasn't able to get the form to display how I wanted to when using a Modelform.
My issue is that in my form save method, a new instance is created, which is not what I want.
Here are the relevant models:
class Client(models.Model):
client_email = models.EmailField(max_length = 254)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
phone = PhoneField(blank=True)
assigned_manager = models.ForeignKey(Manager, on_delete=models.CASCADE, blank=True, null=True)
created_date = models.DateTimeField(default=timezone.now)
#property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
class Manager(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
manager_email = models.EmailField(max_length = 254)
username = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
#property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
My view:
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
form = AssignManagerForm()
if request.method == "POST":
form = AssignManagerForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'mysite/manageclient.html', {})
else:
form = AssignManagerForm()
context = {
'client': client,
'urlid': urlid,
'form': form,
}
return render(request, 'mysite/manageclient.html', context)
And my forms.py
class AssignManagerForm(forms.Form):
full_name = forms.ModelChoiceField(queryset=Manager.objects.all())
def save(self):
data = self.cleaned_data
client = Client(assigned_manager=data['full_name'])
client.save()
What I need to do is pass the urlid in my view to my save method in my forms.py, but I am unsure how to do that. Even if i could do that, I'm not sure how to modify form save to use urlid to refer to a specific record and set only the assigned_manager record.
Additionally, while I want the meta field to be used to display the form, I know it isn't what should be being passed to the assigned_manager field. How would I pass a Manager of instance to establish the foreign key relationship?
edit: edited to correct queryset in forms.py as per comments
Here is a solution using a ModelForm, by using a ModelForm you no longer have to manually set attributes on save or provide initial values when updating an existing instance.
The field assigned_manager will still be named assigned_manager but it's label can be overridden to be whatever you want it to be by passing labels in the ModelForm.Meta
class AssignManagerForm(forms.ModelForm):
class Meta:
model = Client
fields = ['assigned_manager']
labels = {'assigned_manager': 'Full name'}
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
if request.method == "POST":
form = AssignManagerForm(request.POST, instance=client)
if form.is_valid():
client = form.save()
# The general convention is to redirect after a successful POST
else:
form = AssignManagerForm(instance=client)
context = {
'client': client,
'urlid': urlid,
'form': form,
}
return render(request, 'mysite/manageclient.html', context)
Instead of saving it in form, you can directly do this operation in view. For example:
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
if request.method == "POST":
form = AssignManagerForm(request.POST)
if form.is_valid():
client.assigned_manager = form.cleaned_data['full_name']
client.save()
return render(request, 'mysite/manageclient.html', {})

How to retrieve user selections from a previous form

I have several forms that take people through steps and below are the first two and the simplest ones and makes it easy to explain what i am having problem with.
The following two views are login required and contain one form on each. First view is the new_operator where the user fills out a single text input field. Second view is the new_asset where the user fills one text input field as the asset name and selects an operator from the a select/dropdown field. The question is how can i get the form to remember the operator name the user created in the previous form and make it as the default option? To be clear, i still want the user to select any other operator if they choose to do so but i want the option they just created to be the default. Thanks a lot in advance for the help.
First, here are the models:
class OperatorCompany(models.Model):
name = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='operator_added_by', null=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = "Operator Company"
verbose_name_plural = "Operator Companies"
def __str__(self):
return self.name
class AssetName(models.Model):
name = models.CharField(max_length=50, unique=True)
operator = models.ForeignKey(OperatorCompany, related_name='asset', on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='asset_added_by', null=True,
on_delete=models.SET_NULL)
class Meta:
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
views.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset')
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
and following are the forms.py without the init, clean functions and the widgets
class NewOperatorForm(forms.ModelForm):
class Meta:
model = OperatorCompany
fields = ('name',)
class NewAssetForm(forms.ModelForm):
class Meta:
model = AssetName
fields = ('name', 'operator')
To share data between multiple pages, you can use session variables. These are stored on the server and associated to clients according to the session cookie they communicate to the server at every request.
Typically, in the first view, you would add after save():
request.session['latest_created_operator_id'] = newoperator.id
to save in the session the operator id.
And in the second view, after the else,
operator_id = request.session.get('latest_created_operator_id', None)
operator = Operator.objects.filter(id=operator_id).first() # returns None if not found
form = NewAssetForm(initial={'operator': operator})
retrieves the operator and populates the form.
(That's untested code; you may need to edit a bit.)
At a glance, maybe something like this would work.
What you can do is add another URL in urls.py for new_asset which accepts a OperatorCompany id. I don't have your url config but it could be something like:
urls.py
path('wellsurfer/new_asset/<int:operator_id>', new_asset, name='wellsurfer:new_asset_operator')
view.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset', operator_id=newoperator.id)
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request, operator_id=None):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
if operator_id is not None:
operator_company = OperatorCompany.objects.get(pk=operator_id)
form.fields['operator'].initial = operator_company
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})

Saving ManyToManyFields using ModelForms

I am a very new to Django. I am trying to create a Django app. It has portlist and internalapp models. I am using ModelForm to create Internalapp entry. However,the problem is that ports in internalapp model is a ManyToManyField. When I create new entry of internalapp, the ports field( ManyToManyField) does not save data into database.Below,I provide my modles.py,forms.py and views.py code snippets.
Any help would be appreciated.
models.py
class portList(models.Model):
PROTOCOL = (("tcp","TCP"),("udp","UDP"),("icmp","ICMP"))
appName = models.CharField(max_length=50)
serviceName = models.CharField(max_length=50)
protocol = models.CharField(max_length=10, choices=PROTOCOL, default= "TCP")
reviewDt = models.DateField(default=date.today)
status = models.CharField(max_length=20,null=True)
def __str__(self):
return self.serviceName
class internalapp(models.Model):
PERIMETER_DOM = (("All","All"),("ECN","ECN"),("GIZ","GIZ"),)
plAppConfig = models.CharField('PaloAlto Application Config',max_length=1000, blank=False, null=True)
comment = models.CharField('Comments',max_length=500, blank=False, null=True)
createdDt = models.DateField('Creation Date',default=date.today) # Automatically set the field to now when the object is first created.
ports = models.ManyToManyField(portList,blank=True)
def __str__(self):
return self.servGrpNm
forms.py
class InternalappModelForm(forms.ModelForm):
groupInd = forms.IntegerField(label='Group Index',min_value=0)
appGrpNm = forms.CharField(label='Application Group Name',help_text="This field consists of Perimeter Dom, Group Index and Appliction Function name" )
servGrpNm = forms.CharField(label='Service Group Name',help_text="This field consists of Perimeter Dom, Group Index and Appliction Function name")
comment = forms.CharField(widget=forms.Textarea(attrs={'cols': 50, 'rows': 3}),required=False)
ports = forms.ModelMultipleChoiceField(queryset=portList.objects.all(),required=False)
class Meta:
model=internalapp
fields = ['appGrpNm',
'servGrpNm',
'perimeter',
'groupInd',
'appGrpFunc',
'comment',
'ports']
views.py
def new_create(request):
if request.method == 'POST':
form = InternalappModelForm(request.POST)
if form.is_valid():
appObj= internalapp()
appObj = form.save()
return redirect('home')
else:
form = InternalappModelForm()
print(request.POST)
return render(request,'crud/created.html', {'form': form})
You need to manually save the many to many relationships
if form.is_valid():
appObj = form.save()
appObj.ports.add(*form.cleaned_data['ports'])
return redirect('home')

Django modelforms, foreignkey filter for data owned by logged in user [duplicate]

I'm trying to display a form (ModelForm) with a select field filtered by currently logged in user. The select field in this case contains a list of categories. I want to display only the categories which "belong" to the currently logged in user. The category field is a foreign key to the IngredienceCategory model.
Here is what I've come up with so far but it's giving me an error (unexpected keyword queryset). Any ideas what I'm doing wrong?
# models.py
class IngredienceCategory(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
class Ingredience(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(IngredienceCategory, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredients"
def __unicode__(self):
return self.name
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
# views.py
def home(request):
if request.user.is_authenticated():
username = request.user.username
email = request.user.email
foods = Food.objects.filter(user=request.user).order_by('name')
ingredients = Ingredience.objects.filter(user=request.user).order_by('name')
ingrcat = IngredienceCategory.objects.filter(user=request.user)
if request.method == 'POST':
form = IngredienceForm(request.POST)
if form.is_valid():
# Create an instance of Ingredience without saving to the database
ingredience = form.save(commit=False)
ingredience.user = request.user
ingredience.save()
else:
# How to display form with 'category' select list filtered by current user?
form = IngredienceForm(queryset=IngredienceCategory.objects.filter(user=request.user))
context = {}
for i in ingredients:
context[i.category.name.lower()] = context.get(i.category.name.lower(), []) + [i]
context2 = {'username': username, 'email': email, 'foods': foods, 'ingrcat': ingrcat, 'form': form,}
context = dict(context.items() + context2.items())
else:
context = {}
return render_to_response('home.html', context, context_instance=RequestContext(request))
That's happening because ModelForm does not take a queryset keyword.
You can probably achieve this by setting the queryset on the view:
form = IngredienceForm()
form.fields["category"].queryset =
IngredienceCategory.objects.filter(user=request.user)
See related question here.
Here i have another suggestion to solve the problem. You can pass request object in your form object inside view.
In view.py just pass the request object.
form = IngredienceForm(request)
In your forms.py __init__ function also add request object
from models import IngredienceCategory as IC
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
def __init__(self, request, *args, **kwargs):
super(IngredienceForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = IC.objects.filter(user=request.user)
This filter always will be applied whenever you initialize your form .

How to input a model instance in a form field

I have a simple model with 2 classes:
class Company(models.Model):
company_name = models.CharField(default='', max_length=128, blank=True, null=True)
class Visitor(models.Model):
visitor_company = models.ForeignKey(Company)
visitor_name = models.CharField(default='', max_length=128, blank=False, null=False)
I also have a simple form:
class VisitorForm(forms.ModelForm):
visitor_company = forms.CharField()
class Meta:
model = Visitor
fields = "__all__"
And here is the view.py code:
def home(request):
form = Visitor()
if request.method == "POST":
form = Visitor(request.POST)
if form.is_valid():
obj, created = Visitor.objects.get_or_create(**form.cleaned_data)
if created:
messages.add_message(request, messages.SUCCESS, 'Visitor added.')
else:
messages.add_message(request, messages.INFO, 'Visitor exists : %s' % obj.visitor_name)
return redirect('visitors')
context = { 'form': form }
return render(request, "visitors/home.html", context)
I have set visitor_company as a CharField as I want to use Typeahead for users to specify the ForeignKey, rather than Django's built in dropdown (which would appear if I did not set the input type).
However, when I use this method, even if I input a valid company_name in the visitor_company field, I get Cannot assign "XXX": "Visitor.visitor_company" must be a "Company" instance.
How do I input a Company instance? Is it also possible to use get_or_create on a ForeignKey like this if the Company record doesn't exist?
This is untested code, so consider this a starting point, no real solution:
forms.py
class VisitorForm(forms.ModelForm):
visitor_company = forms.CharField()
def clean_visitor_company(self):
vc = self.cleanded_data['visitor_company']
try:
vc_object = Company.objects.get(company_name=vc)
except Company.DoesNotExist:
vc_object = Company.objects.create(company_name=vc)
return vc_object
class Meta:
model = Visitor
fields = "__all__"
views.py
def home(request):
form = VisitorForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('visitors')
return render(request, "visitors/home.html", { 'form': form })