I have a simple CategoryForm which has a hidden field that automatically gets added during save on the front-end. In the Admin panel I would like is_staff users to be able to add a Category while the field is hidden there as well. To superusers I would like the field to be shown. How do I get the excluded fields back in my Admin form?
Forms.py:
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ('category', 'company',)
exclude = ['company']
widgets = {'company': forms.HiddenInput()}
Admin.py:
class CustomCategoryAdmin(admin.ModelAdmin):
form = CategoryForm
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if not request.user.is_superuser:
self.fields = ('category',)
else:
self.fields = ('category', 'company',) # this throws a key error because company is excluded
return form
def save_related(self, request, form, formsets, change):
super(CustomCategoryAdmin, self).save_related(request, form, formsets, change)
company = request.user.company
form.instance.company.add(company) # add object to company field while saving
What I decided to do was create another form named AdminCategoryForm and set the form = AdminCategoryForm as an attribute of CustomCategoryAdmin.
So now my Forms.py:
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ('category', 'company',)
exclude = ['company']
widgets = {
'company': forms.HiddenInput()
}
class AdminCategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ('category', 'company',)
And my Admin.py:
class CustomCategoryAdmin(admin.ModelAdmin):
form = AdminCategoryForm
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if not request.user.is_superuser:
self.fields = ('category',)
else:
self.fields = ('category', 'company',)
self.filter_horizontal = ('company',)
return form
def save_related(self, request, form, formsets, change):
super(CustomCategoryAdmin, self).save_related(request, form, formsets, change)
company = request.user.company
form.instance.company.add(company)
Even though I am repeating my code I feel that this may be the more elegant solution than overriding a ModelAdmin method as it becomes much more clear to outside programmers what is going on here.
If anybody else has other solutions I would love to hear..
Related
I have a form defined from a model class. Depending from the user group of the logged in user, some fields should be remove if logged in user is not belong from certain group.i have a producer group, if logged in user is not belong form producer group then i want to remove the time_pool field from the forms. my code is showing this error 'NoneType' object has no attribute 'groups' . how can i solve this issue?
my code is working i want to know is that a correct way if i initialized the user like this ?
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = [
'title',
'content',
'time_pool',
]
def __init__(self, *args, **kwargs):
super(ArticleForm, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user', None)
if not self.user.groups.filter(name__iexact='producer').exists():
del self.fields['time_pool']
views.py
class NewsCreateView(LoginRequiredMixin, CreateView, SuccessMessageMixin):
form_class = ArticleForm
template_name = 'news/news_create.html'
success_url = '/'
success_message = "%(title)s was created successfully"
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(NewsCreateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
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 am using bootstrap-modal-forms to show a user a formset with some inline forms. It is possible for the user to save the form if data is only entered into the original form, but if the inline formset has data then I get the following error:
'NoneType' object has no attribute 'is_ajax'
The inline formset was working correctly before I tried to implement them in the modal form. The problem seems to arise only when the inline formset (projectimages) is saved it is a NoneType.
My views.py
class ProjectCreate(BSModalCreateView):
form_class = ProjectForm
template_name = 'project_form.html'
success_message = 'Success: %(project_name)s was created.'
def get_success_url(self):
return reverse_lazy('project-detail', kwargs={'project': self.object.slug})
def get_context_data(self, **kwargs):
data = super(ProjectCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['projectimages'] = ProjectFormSet(self.request.POST, self.request.FILES,)
else:
data['projectimages'] = ProjectFormSet()
return data
def form_valid(self, form):
form.instance.date_created = timezone.now()
context = self.get_context_data()
projectimages = context['projectimages']
with transaction.atomic():
self.object = form.save()
if projectimages.is_valid():
projectimages.instance = self.object
projectimages.save()
return super(ProjectCreate, self).form_valid(form)
My forms.py
class ProjectForm(BSModalForm):
class Meta:
model = Project
exclude = ['date_created', 'slug']
ProjectFormSet = inlineformset_factory(
Project,
ProjectImage,
can_delete=True,
form=ProjectForm,
extra=1,
)
My models.py
class Project(models.Model):
project_name = models.CharField(max_length=100)
date_created = models.DateTimeField('Created on')
slug = models.SlugField(unique=True)
def __str__(self):
return self.project_name
def save(self, *args, **kwargs):
self.slug = slugify(str(self))
super(Project, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('project-list')
class ProjectImage(models.Model):
image = models.ImageField(verbose_name='Additional Images', upload_to=project_directory_path)
project = models.ForeignKey(Project, on_delete=models.PROTECT)
annotation = models.CharField(max_length=200, blank=True)
I expect the user to be able to add as many images to the modal formset as they like.
The BSModalForm expects you to initialise it with the request. This happens in your BSModalCreateView for the main form but not for your formset, because you initialise it manually.
So when initialising, just add the form_kwargs attribute:
if self.request.POST:
data['projectimages'] = ProjectFormSet(
self.request.POST, self.request.FILES,
form_kwargs={'request': self.request})
else:
data['projectimages'] = ProjectFormSet(form_kwargs={'request': self.request})
Note that I think the form you set in ProjectFormSet is wrong, because it should be a form for a ProjectImage model, not a Project. It should actually be called ProjectImageFormSet to better reflect what it is.
You probably want to remove form=ProjectForm as it probably doesn't need to be a BSModalForm (not sure about that). In that case you should not pass the request in form_kwargs. If not, you just need to create another ProjectImageForm class.
Finally, you should not return super().form_valid() because that will save the main form a second time (you already did). Do the redirect yourself.
I'm using django form to add custom field in my form, but I want to exclude this custom field in some condition(when user_id is set on url query param), how can I handle it?
Something interesting happens in my try and error, I put my custom field name in Mata.exclude of my form, but this custom field still generates in my form !!!!
Here is my code:
class PushTokenForm(forms.ModelForm):
push_scenario = forms.ChoiceField(
label=_('Push Scenario'),
choices=get_scenarios(),
)
class Meta:
model = PushToken
exclude = ['failed', 'push_scenario']
UPDATE
def get_form(self, request, obj=None, **kwargs):
self.exclude = []
user_id = request.GET['user_id'] if 'user_id' in request.GET else None
if user_id:
self.exclude.append('push_scenario')
form = super(PushTokenAdmin, self).get_form(request, obj, **kwargs)
return form
I do not want to show one form field if user is authenticated.
I tried if not request.user.is_authenticated():
but it's not working.
def create_event(request):
if not request.user.is_authenticated():
CreateEventForm.base_fields['owner_email'] = forms.EmailField(required=True)
event_form = CreateEventForm(request.POST or None, prefix='event')
context = {
'event_form': event_form,
}
if event_form.is_valid():
event = event_form.save(commit=False)
if request.user.is_authenticated():
event.registered_owner = request.user
else:
event.owner_email = event_form.cleaned_data.get('owner_email')
event = event_form.save()
return HttpResponseRedirect('/event-%s' %event.id)
return render(request, 'create_event.html', context)
Form in forms.py
class CreateEventForm(forms.ModelForm):
class Meta:
model = Event
fields = ['title', 'description', 'location', 'duaration', 'private']
You should never modify base_fields; that's a class attribute, so once you add something to it, it's present for all form instances until you explicitly remove it.
Instead, move this logic into the __init__ method for the form itself.
class CreateEventForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
is_authenticated = kwargs.pop('is_authenticated', False)
super(CreateEventForm, self).__init__(*args, **kwargs)
if is_authenticated:
self.fields['owner_email'] = forms.EmailField(required=True)
Now in your view you need to pass that parameter to the form:
event_form = CreateEventForm(request.POST or None, prefix='event', is_authenticated=request.user.is_authenticated())