The objective is to have a simple workflow where an order and associated orderlines (created in a previous step) needs to be approved by the relevant budget holder. The approval form shows all order lines but disables those lines that the current user is not associated with (they should be able to see the overall order but only be able to edit lines that they are permitted to). They should be able to add new lines if necessary. The user needs to decide whether to approve or not (approval radio cannot be blank)
The initial form presents correctly and is able to save inputs correctly when all values are inputted correctly - however, if it fails validation then the incorrect fields get highlighted and their values are cleared.
models.py
class Order(models.Model):
department = models.ForeignKey(user_models.Department, on_delete=models.CASCADE)
location = models.ForeignKey(location_models.Location, on_delete=models.CASCADE, null=True)
description = models.CharField(max_length=30)
project = models.ForeignKey(project_models.Project, on_delete=models.CASCADE)
product = models.ManyToManyField(catalogue_models.Product, through='OrderLine', related_name='orderlines')
total = models.DecimalField(max_digits=20, decimal_places=2, null=True, blank=True)
def __str__(self):
return self.description
class OrderLine(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
project_line = models.ForeignKey(project_models.ProjectLine, on_delete=models.SET_NULL, null=True, blank=False)
product = models.ForeignKey(catalogue_models.Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
price = models.DecimalField(max_digits=20, decimal_places=4)
total = models.DecimalField(max_digits=20, decimal_places=2)
budgetholder_approved = models.BooleanField(null=True)
def get_line_total(self):
total = self.quantity * self.price
return total
def save(self, *args, **kwargs):
self.total = self.get_line_total()
super(OrderLine, self).save(*args, **kwargs)
def __str__(self):
return self.product.name
views.py
class BudgetApprovalView(FlowMixin, generic.UpdateView):
form_class = forms.BudgetHolderApproval
def get_object(self):
return self.activation.process.order
def get_context_data(self, **kwargs):
data = super(BudgetApprovalView, self).get_context_data(**kwargs)
if self.request.POST:
data['formset'] = forms.OrderLineFormet(self.request.POST, instance=self.object)
else:
data['formset'] = forms.OrderLineFormet(instance=self.activation.process.order, form_kwargs={'user': self.request.user})
return data
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
form = forms.BudgetHolderApproval(self.request.POST, instance=self.activation.process.order)
formset = forms.OrderLineFormet(self.request.POST, instance=self.activation.process.order)
if form.is_valid() and formset.is_valid():
return self.is_valid(form, formset)
else:
return self.is_invalid(form, formset)
def is_valid(self, form, formset):
self.object = form.save(commit=False)
self.object.created_by = self.request.user
self.activation.process.order = self.object
with transaction.atomic():
self.object.save()
self.activation.done()
formset.save()
return HttpResponseRedirect(self.get_success_url())
def is_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form, formset=formset))
I have tried a couple of things to figure this out - without success:
to override the clean() method of the ModelForm - however, I cannot figure out how to determine if the submitted form is disabled or not.
forms.py
class OrderForm(forms.ModelForm):
class Meta:
model = models.Order
fields = ['description', 'project', 'location']
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
class OrderLine(forms.ModelForm):
class Meta:
model = models.OrderLine
exclude = ['viewflow']
def __init__(self, *args, **kwargs):
YES_OR_NO = (
(True, 'Yes'),
(False, 'No')
)
self.user = kwargs.pop('user', None)
super(OrderLine, self).__init__(*args, **kwargs)
self.fields['project_line'].queryset = project_models.ProjectLine.objects.none()
self.fields['budgetholder_approved'].widget = forms.RadioSelect(choices=YES_OR_NO)
if self.instance.pk:
self.fields['budgetholder_approved'].required = True
self.fields['order'].disabled = True
self.fields['project_line'].disabled = True
self.fields['product'].disabled = True
self.fields['quantity'].disabled = True
self.fields['price'].disabled = True
self.fields['total'].disabled = True
self.fields['budgetholder_approved'].disabled = True
if 'project' in self.data:
try:
project_id = int(self.data.get('project'))
self.fields['project_line'].queryset = project_models.ProjectLine.objects.filter(project_id=project_id)
except (ValueError, TypeError):
pass
elif self.instance.pk:
self.fields['project_line'].queryset = self.instance.order.project.projectline_set
project_line_id = int(self.instance.project_line.budget_holder.id)
user_id = int(self.user.id)
if project_line_id == user_id:
self.fields['budgetholder_approved'].disabled = False
self.helper = FormHelper()
self.helper.template = 'crispy_forms/templates/bootstrap4/table_inline_formset.html'
self.helper.form_tag = False
def clean(self):
super(OrderLine, self).clean()
pprint(vars(self.instance))
//This just returns a list of fields without any attributes to apply the validation logic
OrderLineFormet = forms.inlineformset_factory(
parent_model=models.Order,
model=models.OrderLine,
form=OrderLine,
extra=2,
min_num=1
)
to override the clean() method of the BaseInlineFormSet - however, I cannot disable the fields in the init or any of the validation rules (it silently fails validation and presents a blank inlineformset on failure - it never gets to clean() method.
forms.py
class OrderForm(forms.ModelForm):
class Meta:
model = models.Order
fields = ['description', 'project', 'location']
def __init__(self, *args, **kwargs):
super(TestOrderForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
class BaseTestOrderLine(forms.BaseInlineFormSet):
def __init__(self, user, *args, **kwargs):
self.user = user
super(BaseTestOrderLine, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.template = 'crispy_forms/templates/bootstrap4/table_inline_formset.html'
self.helper.form_tag = False
// Never gets to the clean method as is_valid fails silently
def clean(self):
super(BaseTestOrderLine, self).clean()
if any(self.errors):
pprint(vars(self.errors))
return
OrderLineFormet = forms.inlineformset_factory(
parent_model=models.Order,
model=models.OrderLine,
formset=BaseTestOrderLine,
exclude=['order'],
extra=2,
min_num=1
)
Edit - reflecting progress based on Dao's suggestion (the form reloads correctly with the validation errors showing correctly)
The only remaining problem is that when the form reloads - the field (budgetholder_approved) that should still be enabled is disabled. One of the two approval checkbox lines lines should be editable
Looks like because you have different formset context data on submit invalid
if self.request.POST:
data['formset'] = forms.OrderLineFormet(self.request.POST, instance=self.activation.process.order, form_kwargs={'user': self.request.user})
else:
data['formset'] = forms.OrderLineFormet(instance=self.activation.process.order, form_kwargs={'user': self.request.user})
return data
Edit for updated Q:
Didn't test this out because of time constraint but because you already initiated the field and overwrite the widget, so if you need to update disabled attr of widget instead of field.
self.fields['budgetholder_approved'].widget = forms.RadioSelect(choices=YES_OR_NO)
self.fields['budgetholder_approved'].widget.attrs['disabled'] = False
Related
I'm using Django 2.1 and PostgreSQL.
My problem is that I'm trying to create a form to edit two different models at the same time. This models are related with a FK, and every example that I see is with the user and profile models, but with that I can't replicate what I really need.
My models simplified to show the related information about them are:
# base model for Campaigns.
class CampaignBase(models.Model):
....
project = models.ForeignKey(Project, on_delete=models.CASCADE)
creation_date = models.DateTimeField(auto_now_add=True)
start_date = models.DateTimeField(null=True, blank=True)
end_date = models.DateTimeField(null=True, blank=True)
....
# define investment campaign made on a project.
class InvestmentCampaign(models.Model):
....
campaign = models.ForeignKey(CampaignBase, on_delete=models.CASCADE, null=True, blank=True)
description = models.CharField(
blank=True,
max_length=25000,
)
....
And the form that I want to create is one that includes the end_date of the FK CampaignBase, and the Description from the InvestmentCampaign.
Now I have this UpdateView to edit the InvestmentCampaign, and I need to adapt to my actual needs, that are also update the CampaignBase model:
class ProjectEditInvestmentCampaignView(LoginRequiredMixin, SuccessMessageMixin, generic.UpdateView):
template_name = 'webplatform/project_edit_investment_campaign.html'
model = InvestmentCampaign
form_class = CreateInvestmentCampaignForm
success_message = 'Investment campaign updated!'
def get_success_url(self):
return reverse_lazy('project-update-investment-campaign', args=(self.kwargs['project'], self.kwargs['pk']))
# Make the view only available for the users with current fields
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
# here you can make your custom validation for any particular user
if request.user != self.object.campaign.project.user:
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
# Set field as current user
def form_valid(self, form):
campaign = InvestmentCampaign.objects.get(pk=self.kwargs['campaign'])
form.instance.campaign = campaign
form.instance.history_change_reason = 'Investment campaign updated'
return super(ProjectEditInvestmentCampaignView, self).form_valid(form)
def get_context_data(self, **kwargs):
project = Project.objects.get(pk=self.kwargs['project'])
context = super(ProjectEditInvestmentCampaignView, self).get_context_data(**kwargs)
context['project'] = project
return context
My forms are:
class CreateCampaignBaseForm(forms.ModelForm):
class Meta:
model = CampaignBase
fields = ('end_date',)
widgets = {
'end_date': DateTimePickerInput(),
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateCampaignBaseForm, self).__init__(*args, **kwargs)
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True
class CreateInvestmentCampaignForm(forms.ModelForm):
class Meta:
model = InvestmentCampaign
fields = ('description')
widgets = {
'description': SummernoteWidget(attrs={'summernote': {
'placeholder': 'Add some details of the Investment Campaign here...'}}),
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateInvestmentCampaignForm, self).__init__(*args, **kwargs)
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True
I've read everywhere that the best way of doing this is using function based views, and call each of the forms that I have and then do the validation. the thing is that I don't know how can I populate the fields with the right object in both forms, and also, I don't know how to do the equivalent of the get_context_data nor getting the self arguments to do the equivalent of the get_success_url (because with function based views I only have the request attr so I can't access the kwargs).
I've seen some people using the django-betterforms, but again, the only examples are with the auth and profile models and I don't see the way to replicate that with my own models.
Thank you very much.
If the only thing you want to change is one field end_date on BaseCampaign, then you should use just one form. Just add end_date as an additional field (e.g. forms.DateTimeField()) on your CreateInvestmentCampaignForm and in your form.valid() method, after saving the form, set the value on the associated campaign:
def form_valid(self, form):
inv_campaign = form.save(commit=False)
inv_campaign.campaign.end_date = form.cleaned_data['end_date']
inv_campaign.campaign.save()
inv_campaign.history_change_reason = ...
return super().form_valid(form)
Here's how to add end_date to your form and initialize it correctly:
class CreateInvestmentCampaignForm(ModelForm):
end_date = forms.DateTimeField(blank=True)
class Meta:
model = InvestmentCampaign
fields = ('description')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.campaign:
self.fields['end_date'].initial = self.instance.campaign.end_date
Based on the conversation on the answer of #dirkgroten, I've developed what worked for me and what I'm actually using, but I market his answer as correct because his code is also functional.
So, meanwhile he is initiating the values on the form, I'm using the view to do that by adding a def get_initial(self): and also adding the validation on the def form_valid(self, form)::
On the view:
...
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(ProjectEditInvestmentCampaignView, self).get_initial()
initial['end_date'] = self.object.campaign.end_date
return initial
...
# Set field as current user
def form_valid(self, form):
form.instance.history_change_reason = 'Investment campaign updated'
is_valid = super(ProjectEditInvestmentCampaignView, self).form_valid(form)
if is_valid:
# the base campaign fields
campaign = form.instance.campaign
campaign.end_date = form.cleaned_data.get("end_date")
campaign.save()
return is_valid
And on the form I just added the end_date field:
class CreateInvestmentCampaignForm(forms.ModelForm):
end_date = forms.DateTimeField()
class Meta:
model = InvestmentCampaign
fields = ('description',)
widgets = {
'description': SummernoteWidget(attrs={'summernote': {
'placeholder': 'Add some details of the Investment Campaign here...'}}),
'end_date': DateTimePickerInput(), # format='%d/%m/%Y %H:%M')
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateInvestmentCampaignForm, self).__init__(*args, **kwargs)
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True
I have a form for creating the object, and it works fine. When I use that form for editing the object, it doesn't work. I did debugging the post method,and it also works fine, the form is valid, the redirect work, the success message appear, but not update the object. The form instance is also work correctly. It just don't update
# models.py
class Item(models.Model):
status_choices = (
('rent','Rent'),
('give', 'Give'),
('share','Share'),
)
item_types = (
('book','Book'),
('movie','Movie',),
('data','Data'),
('other','Other'),
)
title = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200, db_index=True, blank=True,unique=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
cover = models.ImageField(upload_to='items/%Y/%m/%d',blank=True)
link = models.URLField(blank=True)
description = models.TextField(max_length=500, blank=True)
status = models.CharField(max_length=10,choices=status_choices,default='Share')
item = models.CharField(max_length=10, choices=item_types,default='Data', verbose_name='Item Type')
publish = models.DateTimeField(auto_now=True,null=True)
class Meta:
ordering = ('-publish',)
index_together = (('id', 'slug'),)
def __str__(self):
return '{} : <{}> for {}'.format(self.title,self.item,self.status)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Item, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('item_edit', kwargs={'slug':self.slug})
#forms.py
class ItemShareForm(forms.ModelForm):
class Meta:
model = Item
fields = ('title', 'cover', 'link', 'description', 'status', 'item')
widgets = {
'description' : forms.Textarea(),
}
#views.py
#login_required
def item_edit(request,slug):
instance = get_object_or_404(Item,slug=slug)
if request.method == 'POST': #check the request method
edit_form = ItemShareForm(request.POST ,instance=instance)
if edit_form.is_valid(): # check the form validation
update_item = edit_form.save(commit=False)
update_item.owner = request.user #assign the owner
update_item.save() # update the instance
messages.success(request,'Your item has been updated successfully') # writing the message to user
return redirect('/')
else:
messages.error(request,'Error updating your item...')
else:
edit_form = ItemShareForm(instance=instance)
return render(request, 'account/share.html',{'itemform':edit_form})*
You have overridden you model save method like so:
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Item, self).save(*args, **kwargs)
This means that the object will only ever get saved if the slug is empty - i.e., it will only ever get saved once. Any future calls to save will not execute that if block, and nothing will happen.
You probably mean to do this instead - note indentation of the last line:
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Item, self).save(*args, **kwargs)
In my django app I have a Myuser(User) class. It inherits the User class.
When a new user is created the Myuser table is poplulated.
myusers.py
class Myuser(User):
address = models.CharField(max_length=40)
pobox = models.CharField(max_length=40)
models.py
class Someclass(models.Model):
objectid = models.IntegerField()
objecttype = models.CharField(max_length=200)
created = models.DateTimeField(default=timezone.now)
modified = models.DateTimeField(auto_now=True)
class Someotherclass(Someclass):
status = models.IntegerField(default=0,)
name = models.CharField(max_length=200)
created = models.DateTimeField(default=timezone.now)
modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User)
forms.py
class SomeotherclassForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
self.user = kwargs.pop('user')
self.app = kwargs.pop('app')
self.table = kwargs.pop('table')
self.mytype = kwargs.pop('mytype')
initial = kwargs.get('initial', {})
super(SomeotherclassForm, self).__init__(*args, **kwargs)
create.py
class DataCreate(CreateView):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
#some code here not relevant at all
def get_form_kwargs(self):
kwargs = super(DataCreate, self).get_form_kwargs()
objectid = self.request.GET.get('objectid',None)
objecttype = self.request.GET.get('objecttype',None)
kwargs.update({'mytype': objecttype})
kwargs.update({'request': self.request})
kwargs.update({'user': self.request.user})
kwargs.update({'app': self.app})
kwargs.update({'table': self.kwargs['table'].lower()})
return kwargs
def form_valid(self, form):
obj = form.save(commit=False)
group = ''
if not self.request.user.is_superuser:
group = MyUser.objects.get(user_ptr_id=self.request.user.pk)
else:
groups = self.request.user.groups.all()
if self.kwargs['table'] == 'Myprotocol':
obj = form.save(commit=False)
table = eval(self.request.GET.get('objecttype',None).title()).objects.get(pk=int(self.request.GET.get('objectid',None)))
obj.objectid = table.pk
obj.objecttype = table.__class__.__name__.lower()
obj.user_id = self.request.user.pk
obj.save()
else:
obj = form.save()
if self.request.POST.get('is_popup'):
check = int(self.kwargs['is_list'])
if self.kwargs['table'] == 'Someclass':
popup = 1
a = checkPopup2(obj,check,popup,obj.pk)
else:
a = checkPopup(obj,check)
return a
else:
return super(DataCreate, self).form_valid(form)
When I have logged in as a regular user ,everything works fine.
When I log in as a superuser, I get form error that objecttype,objectid and user are not filled.
In my attempts to troubleshoot it , I realized that when I am logged in as a superuser ,it dowsn't reach the form_valid() function.
I can't figure out why that is happening.
Any suggestions or advice on how to troubleshoot it?
I have been Googling around for this question for quite long. And sadly no luck. Indeed I found questions like SO question and SO question. I would say I found myself having a very similar question to the first one. However, I do not understand the discussions under that question.
So first of all, to make it clear, I did not even get formset.is_valid() to return True--that is the problem. There are indeed uniqueness check but my edit should be able to pass that check in the database level.
And here comes my code:
### models.py
class DataAccess(models.Model):
user = models.ForeignKey(User, models.CASCADE)
project_data = models.ForeignKey(ProjectData, models.CASCADE)
role = models.CharField(
max_length=2, choices=ROLES, default='N'
)
created_at = models.DateTimeField(auto_now=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = (('user', 'project_data', 'role'),)
# query from project data should happen more
index_together = [['project_data', 'user', 'role']]
def __str__(self):
return '{}&{}&{}'.format(self.user, self.project_data, self.role)
def save(self, *args, **kwargs):
curr = datetime.now()
if not self.created_at:
self.created_at = curr
self.updated_at = curr
return super(DataAccess, self).save(*args, **kwargs)
class DataSyncPath(models.Model):
server = models.ForeignKey(Server, models.CASCADE)
project_data = models.ForeignKey(ProjectData, models.CASCADE)
path = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = (('server', 'project_data',),)
index_together = [['project_data', 'server']]
def __str__(self):
return '{}&{}'.format(self.server, self.project_data)
def save(self, *args, **kwargs):
curr = datetime.now()
if not self.created_at:
self.created_at = curr
self.updated_at = curr
return super(DataSyncPath, self).save(*args, **kwargs)
### forms.py
class DataAccessForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DataAccessForm, self).__init__(*args, **kwargs)
helper = FormHelper()
helper.template = 'bootstrap/table_inline_formset.html'
self.helper = helper
self.helper.form_tag = False
class Meta:
model = DataAccess
exclude = ['created_at', 'updated_at']
def get_data_access_formset(initial=[], extra=3):
return inlineformset_factory(
ProjectData, DataAccess,
form=DataAccessForm, extra=extra + len(initial)
)
class DataSyncPathForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DataSyncPathForm, self).__init__(*args, **kwargs)
helper = FormHelper()
helper.template = 'bootstrap/table_inline_formset.html'
self.helper = helper
self.helper.form_tag = False
class Meta:
model = DataSyncPath
exclude = ['created_at', 'updated_at']
def clean(self):
super(DataSyncPathForm, self).clean()
if (self.cleaned_data['id'] is None and 'path' in self.errors and
self.errors['path'][0] == 'This field is required.'):
# a special hack to silent path error
self.errors.pop('path')
def get_data_sync_path_formset(initial=[], extra=0):
return inlineformset_factory(
ProjectData, DataSyncPath, can_delete=False,
form=DataSyncPathForm, extra=extra + len(initial)
)
### views.py, import and everything is taken care of already
class ProjectDataUpdateView(LoginRequiredMixin, UpdateView):
login_url = reverse_lazy('login')
redirect_field_name = 'redirect_to'
template_name = 'project_data/project_data_form.html'
form_class = ProjectDataForm
context_object_name = 'project_data'
pk_url_kwarg = 'project_data_id'
success_url = reverse_lazy('data_projects:project_data_list')
def get_queryset(self):
return dbmanager.get_project_data_by_user(
self.request.user
)
def get_initial(self):
initial = super(ProjectDataUpdateView, self).get_initial()
try:
initial['phabricator_links'] = ','.join(json.loads(
self.object.phabricator_links))
except Exception:
initial['phabricator_links'] = ''
return initial
def get_data_access_initial(self):
data_access_queryset = self.object.dataaccess_set.all()
data_access_initial = [
model_to_dict(obj) for obj in data_access_queryset
]
return data_access_initial
def get_data_sync_path_initial(self):
datasyncpath_set = self.object.datasyncpath_set.all()
sync_path_initial = [
model_to_dict(obj) for obj in datasyncpath_set
]
servers = self.request.user.server_set.all()
missing_servers = set(
[server.id for server in servers]) - set(
[obj['server'] for obj in sync_path_initial]
)
for server_id in missing_servers:
sync_path_initial.append({'server': server_id})
return sync_path_initial
def process_context_data(self, data):
data_access_initial = self.get_data_access_initial()
sync_path_initial = self.get_data_sync_path_initial()
if self.request.POST:
if (self.request.user.is_superuser or
self.request.user.groups.filter(name='Manager').exists()):
data['data_access_formset'] = get_data_access_formset()(
self.request.POST, self.request.FILES,
instance=self.object, initial=data_access_initial,
queryset=self.object.dataaccess_set.all()
)
data['data_sync_path_formset'] = get_data_sync_path_formset()(
self.request.POST, self.request.FILES,
instance=self.object, initial=sync_path_initial,
queryset=self.object.datasyncpath_set.all()
)
else:
data['data_access_formset'] = get_data_access_formset(
initial=data_access_initial
)(initial=data_access_initial)
data['data_sync_path_formset'] = get_data_sync_path_formset(
initial=sync_path_initial
)(initial=sync_path_initial)
return data
def get_context_data(self, **kwargs):
data = super(ProjectDataUpdateView, self).get_context_data(**kwargs)
data_access_formset = kwargs.pop('data_access_formset', None)
data_sync_path_formset = kwargs.pop('data_sync_path_formset', None)
if data_access_formset and data_sync_path_formset:
data['data_access_formset'] = data_access_formset
data['data_sync_path_formset'] = data_sync_path_formset
else:
data = self.process_context_data(data)
data['is_data_sync_path_set'] = True
return data
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not (self.request.user.is_superuser or
self.request.user.groups.filter(name='Manager').exists()):
self.request.POST['name'] = self.object.name
self.request.POST['dmgr'] = self.object.dmgr
form_class = self.get_form_class()
form = self.get_form(form_class)
context_data = self.get_context_data()
data_access_formset = context_data.get('data_access_formset')
sync_path_formset = context_data.get('data_sync_path_formset')
if (form.is_valid() and
data_access_formset.is_valid() and
sync_path_formset.is_valid()):
return self.form_valid(
form, data_access_formset, sync_path_formset)
else:
return self.form_invalid(
form, data_access_formset, sync_path_formset)
def form_valid(
self, form, data_access_formset, sync_path_formset, **kwargs):
for deleted_form in data_access_formset.deleted_forms:
try:
deleted_form.cleaned_data['id'].delete()
except Exception:
pass
self.object = form.save()
data_access_formset.save()
sync_path_formset.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(
self, form, data_access_formset, sync_path_formset, **kwargs):
return self.render_to_response(
self.get_context_data(
form=form, data_access_formset=data_access_formset,
sync_path_formset=sync_path_formset
)
)
I have also put breakpoint in post method of the UpdateView class:
ipdb> sync_path_formset.is_valid()
False
ipdb> sync_path_formset.forms[0]
<DataSyncPathForm bound=True, valid=False, fields=(server;project_data;path;id;DELETE)>
ipdb> sync_path_formset.forms[0].cleaned_data
{'DELETE': False, 'project_data': <ProjectData: bbb>, 'path': 'that', 'server': <Server: jgu-pc>, 'id': <DataSyncPath: jgu-pc&bbb>}
ipdb> sync_path_formset.forms[0].instance
<DataSyncPath: jgu-pc&bbb>
ipdb> sync_path_formset.forms[0].save(commit=False)
*** ValueError: The DataSyncPath could not be created because the data didn't validate.
ipdb> sync_path_formset.forms[0].has_changed()
True
So clearly the instance exists. But it just tries to create another instance anyway.
Here is the Model class
class Album(models.Model):
name = models.CharField(max_length=256)
public = models.BooleanField(default=False)
user = models.ForeignKey(get_user_model())
class Meta:
unique_together = (("name", "user"),)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('photos:index')
Here is the View
class Create(CreateView):
model = Album
fields = ['name', 'public']
form_class = AlbumCreateForm
def form_valid(self, form):
form.instance.user = self.request.user
return super(Create, self).form_valid(form)
And here is the form Class
class AlbumCreateForm(ModelForm):
class Meta:
model = Album
fields = ['name', 'public']
labels = {'name': '', 'public': 'Public'}
def __init__(self,*args, **kwargs):
super(AlbumCreateForm, self).__init__(*args, **kwargs)
self.fields['name'].widget = forms.TextInput(
attrs={'placeholder': 'name'})
I can create album just fine, but what I would like to do is prevent duplicate albums from being created for a particular user. For example if user1 has already created album1, he should not be able to create another album named album1.
The place where I can do it is AlbumCreateForm. But AlbumCreateForm does not have any knowledge of current user. Any idea how it can be accomplished?
Pass user to form - add him to form kwargs by adding this method to view:
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(Create, self).get_form_kwargs(*args, **kwargs)
kwargs['user'] = self.request.user
return kwargs
Get user in form and check:
def __init__(self,*args, **kwargs):
self.user = kwargs.pop('user') # this line added
super(AlbumCreateForm, self).__init__(*args, **kwargs)
self.fields['name'].widget = forms.TextInput(
attrs={'placeholder': 'name'})
def clean(self):
if Album.objects.filter(user=self.user).exists():
raise forms.ValidationError('Error description')