I have a CharField (displayed_fields) in my model that I display in my form as a MultipleChoiceField. Currently, the form loads with nothing selected, even if the model's displayed_fields is non-empty.
I'd like the form to initialize to the previously selected items being selected. So far, I've tried addint different values of intial, including initial=ExamplePlugin.EMAIL_COLUMN and initial={'displayed_fields': ['name', 'office', 'phone']}, to forms.py's field declaration, which doesn't seem to change anything. Is it possible to initialize it like this, and if not, is there a better model to be using than CharField?
models.py:
class ExamplePlugin(CMSPlugin):
NAME_COLUMN = 'name'
OFFICE_COLUMN = 'office'
PHONE_COLUMN = 'phone'
EMAIL_COLUMN = 'email'
TITLE_COLUMN = 'title'
COLUMN_CHOICES = (
(NAME_COLUMN, 'First and Last Name'),
(OFFICE_COLUMN, 'Office Location'),
(PHONE_COLUMN, 'Phone Number'),
(EMAIL_COLUMN, 'Email Address'),
(TITLE_COLUMN, 'Title'),
)
displayed_fields = models.CharField(blank=False, verbose_name='Fields to show', max_length=255)
forms.py:
class ExampleForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ExampleForm, self).__init__(*args, **kwargs)
displayed_fields = MultipleChoiceField(choices=ExamplePlugin.COLUMN_CHOICES, help_text="Select columns that you would like to appear.")
class Meta:
model = ExamplePlugin
I think you should do:
class ExampleForm(ModelForm):
displayed_fields = MultipleChoiceField(choices=ExamplePlugin.COLUMN_CHOICES, help_text="Select columns that you would like to appear.", initial=['name', 'office', 'phone'])
def __init__(self, *args, **kwargs):
super(ExampleForm, self).__init__(*args, **kwargs)
class Meta:
model = ExamplePlugin
MultipleChoiceField accepts a list as its default, I guess.
Related
I would like to have a form with the preselected checkboxes of a ManyToManyField.
models.py
class Store(models.Model):
...
class Brand(models.Model):
stores = models.ManyToManyField(Store, blank=True, related_name="brands")
forms.py
class StoreForm(ModelForm):
class Meta:
model = Store
fields = ('brands',)
I get this exception:
django.core.exceptions.FieldError: Unknown field(s) (brands) specified for Store
I know that I can add the field manually to the class:
brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
If I do this the checkboxes are not preselected.
How is it possible to include the ManyToMany field from "the other side" of the model (from Store)?
#hedgie To change the field in the other model is not a good option for me because I use it already.
But the __init__() was a good hint. I come up with this solution and it seems to work.
class StoreForm(ModelForm):
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
brand_ids = [t.pk for t in kwargs['instance'].brands.all()]
kwargs['initial'] = {
'brands': brand_ids,
}
super().__init__(*args, **kwargs)
# https://stackoverflow.com/questions/49932426/save-many-to-many-field-django-forms
def save(self, commit=True):
# Get the unsaved Pizza instance
instance = forms.ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
# This is where we actually link the pizza with toppings
instance.brands.clear()
for brand in self.cleaned_data['brands']:
instance.brands.add(brand)
self.save_m2m = save_m2m
# Do we need to save all changes now?
# Just like this
# if commit:
instance.save()
self.save_m2m()
return instance
brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
Though it seems to be not very elegant. I wonder why django does not support a better way.
One possibility is to define the field on the "other" model. So instead of writing this:
class Store(models.Model):
...
class Brand(models.Model):
stores = models.ManyToManyField(Store, blank=True, related_name="brands")
You can write this:
class Brand(models.Model):
...
class Store(models.Model):
brands = models.ManyToManyField(Brand, blank=True, related_name="stores")
Or, if you have manually added the field to the form, you could populate its initial value in the form's __init__() method.
Is there a way to update a unique field in update view?
I have a model that has a name and age field but when I try to update the age without even changing the value of the name, it returns an error that the name already exists in the database
models.py
class MyModel(models.Model)
name = models.CharField(max_length=200, unique=True)
age = models.IntegerField()
views.py
class MyModelUpdateView(UpdateView):
def get(self):
self.object = self.get_object()
my_model = self.object
form = MyModelForm(instance=my_model)
return self.render_to_response(
self.get_context_data(pk=my_model.pk, form=form)
)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
my_model = self.object
form = MyModelForm(data=request.POST, instance=my_model)
if form.is_valid():
form.save()
return some_url
return self.render_to_response(
self.get_context_data(pk=my_model.pk, form=form)
)
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = (
'name',
'age',
)
def clean(self):
cleaned_data = super().clean()
if MyModel.objects.filter(
active=True, name=cleaned_data.get('name')
).exists():
raise forms.ValidationError('MyModel already exists.')
return cleaned_data
What am I missing here? Thank you.
Since you update a model, and you do not change the name, of course a record with that name already exists: that specific record. You thus should alter the checking code, to:
class MyModelForm(forms.ModelForm):
def clean(self, *args, **kwargs):
cleaned_data = super().clean(*args, **kwargs)
if MyModel.objects.exclude(pk=self.instance.pk).filter(
active=True, name=cleaned_data.get('name')
).exists():
raise forms.ValidationError('MyModel already exists.')
return cleaned_data
class Meta:
model = MyModel
fields = ('name', 'age')
Please do not alter the boilerplate logic of the UpdateView, you can easily implement this with:
class MyModelUpdateView(UpdateView):
form_class = MyModelForm
success_url = 'some url'
That being said, since if you already have set the field to unique=True, then there is no need to implement the check yourself. It seems here, that you already have a unique=True constraint:
class MyModel(models.Model)
name = models.CharField(max_length=200, unique=True)
age = models.IntegerField()
In that case, you can simply let the ModelForm do the work, so then your form looks like:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('name', 'age')
It is only if you want a more sophisticated uniqness (like with active=True?), and you can not represent it (easily) you should do your own validation.
I'm trying to add a nested serializer to an existing serializer based on some criteria of the parent model, not a foreign key. The use case is to return a 'Research' object with an array of 'ResearchTemplate' objects that are identified by filtering on a Postgres ArrayField.
Models
class Research(TimeStampedModel):
category = models.CharField(max_length=100, choices=RESEARCH_TEMPLATE_CATEGORIES, default='quote')
body = models.CharField(max_length=1000, blank=True, default='') #The body of text comprising the nugget
additionaldata = JSONField(null=True) # all the varying values to be merged into a wrapper
def __str__(self):
return self.body
class Meta:
ordering = ('created',)
class ResearchTemplate(TimeStampedModel):
template = models.TextField(blank=True, default='')
category = models.CharField(max_length=100, choices=RESEARCH_TEMPLATE_CATEGORIES, default='quote')
mergefields = ArrayField(models.CharField(max_length=200), blank=True)
def save(self, *args, **kwargs):
merges = re.findall("{{(.*?)}}", self.template) #get all the template names from within the mustaches
self.mergefields = list(set(merges)) #TODO: Make Unique
super(TimeStampedModel, self).save(*args, **kwargs)
def __str__(self):
return self.wrapper
class Meta:
ordering = ('created',)
Serializers
class ResearchSerializer(serializers.ModelSerializer):
templates = ResearchTemplateSerializer(many=True)
class Meta:
model = Research
fields = ('id', 'created', 'speaker', 'body', 'templates')
class ResearchTemplateSerializer(serializers.RelatedField):
def get_queryset(self, values):
return ResearchTemplate.objects.filter(mergefields__contained_by=['django']) #This must an array of keys from the Research object's JSON field
class Meta:
model = ResearchTemplate
fields = ('id', 'template')
I've been able to nest serializers when there is a foreign key mapping them, however I am unable to do so with a custom queryset. Perhaps I'm not thinking about this properly, and I require some form of 'relationship' field on the Research model.
How can I nest a serialized list of all rows that are returned from a filter with values specified from the parent model?
You can use DRF's SerializerMethodField.
Define your ResearchTemplateSerializer as a normal ModelSerializer, not as a RelatedField.
Then replace your ResearchSerializer with this:
class ResearchSerializer(serializers.ModelSerializer):
templates = serializers.SerializerMethodField()
class Meta:
model = Research
fields = ('id', 'created', 'speaker', 'body', 'templates')
def get_templates(self, obj):
values = obj.get_values() # whatever your filter values are. obj is the Research instance
templates = ResearchTemplate.objects.filter(mergefields__contained_by=values) # Or whatever queryset filter
return ResearchTemplateSerializer(templates, many=True).data
When i try to save ModelForm with models.ManyToManyField it throws an error 'Value is not a valid value for a primary key'.
Also the initial values that I set in 'def __init__' only works in admin panel, but not in frontend.
And how do i configure it to show only if no data in database, but if data is in it will shows initial in form (to edit)? By the wa there is no little '+' near the field, so how user will add values?
forms.py:
class UserprojectForm(forms.ModelForm):
class Meta:
model = Userproject
fields = ['name', 'user', 'description', 'vk_groups', 'source_name', 'date_updated', 'date_until'] #'date_created', cannot add django.core.exceptions.FieldError: Unknown field(s) (date_created) specified for Userproject
# widgets = {
# 'vk_groups': forms.Textarea(attrs={'cols': 80, 'rows': 10}),
# 'source_name': forms.Textarea(attrs={'cols': 80, 'rows': 5}),
# }
def __init__(self, *args, **kwargs):
super(UserprojectForm, self).__init__(*args, **kwargs)
#vk_groups = [(v.id,v.buid) for v in Userproject.objects.exclude(buid='').order_by('buid')]
vk_groups = (('https://vk.com/southitpark','https://vk.com/southitpark'), ('https://vk.com/graphgrail', 'https://vk.com/graphgrail'))
source_names = (('Вконтакте', 'Вконтакте'), ('Facebook', 'Facebook'), ('Twitter (скоро)', 'Twitter (скоро)'))
self.fields['vk_groups'].choices = vk_groups
self.fields['source_name'].choices = source_names
models.py:
class Userproject(models.Model):
class Meta:
verbose_name = u'Проект мониторинга'
verbose_name_plural = u'Проекты мониторинга'
vk_groups = models.ManyToManyField(Vkgroupstomonitor, null=True, blank=True, related_name="vkgroups", verbose_name=_("Группы Вконтакте"))
source_name = models.ManyToManyField(Sourcetomonitor, null=True, blank=True, related_name="sourcename", verbose_name=_("Название источника"))
name = models.CharField(u'Название проекта', unique=True, max_length=255)
.......
views.py:
class UserprojectCreate(CreateView):
model = Userproject
template_name = 'userproject_form.html'
success_url = reverse_lazy('userproject_list')
fields = ['name', 'description', 'vk_groups', 'source_name', 'date_updated']
def form_valid(self, form):
form.instance.user = self.request.user
return super(UserprojectCreate, self).form_valid(form)
Consider to specify queryset instead of choices. The latter does not seem to be a part of design of this field class at all.
BTW, in my opinion, it would be better to include this into explicit declaration of the form`s fields rather than patch them in init method.
I have a simple form in Django that looks like this:
class SettingForm(forms.Form):
theme = forms.CharField(rrequired=True,
initial='multgi'
)
defaultinputmessage = forms.CharField(required=True,
initial='Type here to begin..'
)
...and the model to store it looks like:
class Setting(models.Model):
name = models.CharField(
null=False, max_length=255
)
value= models.CharField(
null=False, max_length=255
)
When the form is submitted, how can i store the form fields as key value pairs and then when the page is rendered, how can I initialize the form with the key's value. I've tried looking for an implementation of this but have been unable to find one.
Any help?
Thanks.
I'm assuming you want to store 'theme' as the name and the value as the value, same for defaultinputmessage. If that's the case, this should work:
form = SettingForm({'theme': 'sometheme', 'defaultinputmessage': 'hello'})
if form.is_valid():
for key in form.fields.keys():
setting = Setting.objects.create(name=key, value=form.cleaned_data[key])
Here's how I did it.
I needed to do this because I had a Model that stored information as key value pairs and I needed to build a ModelForm on that Model but the ModelForm should display the key-value pairs as fields i.e. pivot the rows to columns. By default, the get() method of the Model always returns a Model instance of itself and I needed to use a custom Model. Here's what my key-value pair model looked like:
class Setting(models.Model):
domain = models.ForeignKey(Domain)
name = models.CharField(null=False, max_length=255)
value = models.CharField(null=False, max_length=255)
objects = SettingManager()
I built a custom manager on this to override the get() method:
class SettingManager(models.Manager):
def get(self, *args, **kwargs):
from modules.customer.proxies import *
from modules.customer.models import *
object = type('DomainSettings', (SettingProxy,), {'__module__' : 'modules.customer'})()
for pair in self.filter(*args, **kwargs): setattr(object, pair.name, pair.value)
setattr(object, 'domain', Domain.objects.get(id=int(kwargs['domain__exact'])))
return object
This Manager would instantiate an instance of this abstract model. (Abstract models don't have tables so Django doesn't throw up errors)
class SettingProxy(models.Model):
domain = models.ForeignKey(Domain, null=False, verbose_name="Domain")
theme = models.CharField(null=False, default='mytheme', max_length=16)
message = models.CharField(null=False, default='Waddup', max_length=64)
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(SettingProxy, self).__init__(*args, **kwargs)
for field in self._meta.fields:
if isinstance(field, models.AutoField):
del field
def save(self, *args, **kwargs):
with transaction.commit_on_success():
Setting.objects.filter(domain=self.domain).delete()
for field in self._meta.fields:
if isinstance(field, models.ForeignKey) or isinstance(field, models.AutoField):
continue
else:
print field.name + ': ' + field.value_to_string(self)
Setting.objects.create(domain=self.domain,
name=field.name, value=field.value_to_string(self)
)
This proxy has all the fields that I'd like display in my ModelFom and store as key-value pairs in my model. Now if I ever needed to add more fields, I could simply modify this abstract model and not have to edit the actual model itself. Now that I have a model, I can simply build a ModelForm on it like so:
class SettingsForm(forms.ModelForm):
class Meta:
model = SettingProxy
exclude = ('domain',)
def save(self, domain, *args, **kwargs):
print self.cleaned_data
commit = kwargs.get('commit', True)
kwargs['commit'] = False
setting = super(SettingsForm, self).save(*args, **kwargs)
setting.domain = domain
if commit:
setting.save()
return setting
I hope this helps. It required a lot of digging through the API docs to figure this out.