I have a baseform with over 20 fields. Then I have about 15 other forms inheriting from that form, passing in a parameter called fields which the baseform uses to delete all other fields. Best explain via example:
class BaseForm(forms.Form):
reportid = forms.HiddenInput()
fromdate = forms.DateField(label=_("From"), widget=widgets.AdminDateWidget())
todate = forms.DateField(label=_("To"), widget=widgets.AdminDateWidget())
sort_by = forms.ChoiceField(label=_("Sort by"), choices=[])
.......
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields')
#Pseudo:
***del self.fields[field] for field not in fields***
class SubForm(forms.Form):
def __init__(self, *args, **kwargs):
fields = ['reportid', 'todate']
super(SubForm, self).__init__(fields=fields, *args, **kwargs)
The resulting form would then look like this:
class SubForm(forms.Form):
reportid = forms.HiddenInput()
todate = forms.DateField(label=_("To"), widget=widgets.AdminDateWidget())
My problem is that when the BaseForm is initialized for the first time, the labels are bound to the fields with the active language, and when another user logs in with another language setting (or the current user changes languages) the field labels don't update.
I've come to a solution using a dict like this:
labels = {
'todate': lambda: _("To"),
'fromdate': lambda: _("From"),
.....
}
and then when initializing the baseform looping through all fields and setting
self.fields[field].widget.label = labels[field]()
Do I have any nicer (read: more pythonic) way of achieving this?
Django provides _lazy variants of the translation functions (for example ugettext_lazy) so you can ark strings for translations at the access time (as opposed to when the translation function is called).
It's documented in details at https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#lazy-translation
Related
I have to models which are connected by a M2M-Field realized by another Class ComponentInModule, so that I can add there the extra information, how often a component is in the module.
class Module(models.Model):
...
component = models.ManyToManyField(Component, through="ComponentInModule")
class Component(models.Model):
...
class ComponentInModule(models.Model):
module = models.ForeignKey(InfrastructureModule, on_delete=models.CASCADE)
component = models.ForeignKey(InfrastructureComponent, on_delete=models.CASCADE)
amount = models.IntegerField(default=1)
Now I am trying to load a Module as a form with its corresponding Components as a formset.
class ComponentForm(ModelForm):
amount = IntegerField()
module = InfrastructureModule.objects.get(id=x)
ComponentFormSet = modelformset_factory(Component, form=ComponentForm, extra=0)
component_formset = ComponentFormSet(queryset=module.get_components())
As you can see my ComponentForm has the extra field for the amount. The question now is, how can I pass the value of amount to the Formset on creation, so that all forms are initialized with the right value? With a single Form it's no problem, because I can just pass the value to the __init__ function of the form and put it into the amount field self.fields["amount"].initial = amount. I tried passing a list of values to the formset with form_kwargs, but then I got the problem, that in the __init__function I dont know which of the values in the list is the right one right now.
Is there any way to do this using formsets? Or is there some other option I am missing how you can include the extra fields from a M2M-relation in a ModelForm?
So I worked it out. I made a custom BaseModelFormSet class:
class BaseCompFormset(BaseModelFormSet):
def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
amount = kwargs["amount"][index]
return {"amount": amount}
Adjusted the __init__ function of the form:
def __init__(self, *args, **kwargs):
amount = kwargs.pop("amount")
super(ComponentForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields["amount"].initial = amount
And used those to create my modelformset_factory:
amounts = [x.amount for x in module.get_components_in_module()]
ComponentFormSet = modelformset_factory(Component, formset=BaseCompFormset, form=ComponentForm, extra=0)
component_formset = ComponentFormSet(queryset=module.get_components(), form_kwargs={'amount':amounts})
And now succesfully got the forms of the formset with the right initial value for amount!
I'm new to Django and I'm having a hard time understanding forms when the data to choose from are not taken from the database nor user input that they're generated on the go.
I currently have a template with a single ChoiceField. The data inside this field aren't fixed and they're calculated on the go once the page is requested. To calculate it I need the username of the User who is logged in. Basically, the calculation returns a list of lists in the form of ((title, id),(title,id),(title,id)), etc. that I need to put into the ChoiceField to make the User choose from one of the options.
Now, I'm not understanding how to pass the calculated list of lists to the form. I've tried to add the calculations inside the form as below but it is clearly the wrong way.
The main issue is that, to calculate my list of lists, I need the request value, and I don't know how to access it from the form.
Another idea was to add the generate_selection function inside the init but then I don't know how to pass main_playlist to being able to add it to ChoiceField
Below my not working forms.py
forms.py
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=HERE_SHOULD_GO_main_playlist)
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
main_playlist = []
for playlists in user_playlists["items"]:
playlists_list = []
playlists_list.append(playlists['name'])
playlists_list.append(playlists['id'])
main_playlist.append(playlists_list)
return main_playlist
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
class Meta:
model = User
fields = ('playlists',)
The views should be something like below so I'm able to pass the request
views.py
form = ChoosePlaylistForm(request=request)
Maybe overriding the field choices in the form constructor would work:
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=())
class Meta:
model = User
fields = ('playlists',)
def __init__(self, *args, request=None, **kwargs):
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
self.request = request
self.fields['playlists'].choices = self.generate_selection()
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
choices = []
for playlist in user_playlists["items"]:
playlist_choice = (playlist["name"], playlist["id"])
choices.append(playlist_choice)
return choices
When working with Django model forms, I often do something like this:
def my_view(request):
new_entry = MyModel(name='a')
form = MyModelForm(instance=new_entry)
...
I want to do something similar with a modelformset. Something like this would be ideal:
def my_view(request):
MyFormSet = modelformset_factory(MyModel, form=MyModelForm)
new_entries = [MyModel(name='a'), MyModel(name='b')]
formset = MyFormSet(instances=new_entries) # off course this does not work
...
Since the items are not stored in the database yet, I can't set the instances using a queryset. If I want to use initial I have to declare the fields in the form, which is not ideal and seems a bit hacky.
Any suggestions how I can set the instances of each modelform in a modelformset?
Ok, I think I've found a solution.
class FormSetWithInstances(BaseFormSet):
def __init__(self, *args, **kwargs):
self.instances = kwargs.pop('instances')
super(FormSetWithInstances, self).__init__(*args, **kwargs)
def get_form_kwargs(self, index):
form_kwargs = super(FormSetWithInstances, self).get_form_kwargs(index)
if index < len(self.instances):
form_kwargs['instance'] = self.instances[index]
return form_kwargs
Be careful when using this modelformsets or inlinemodelformsets, as the queryset will override the instance you set.
An alternative approach:
class FormSetWithInstances(BaseFormSet):
def get_form_kwargs(self, index):
kwargs = super(FormSetWithInstances, self).get_form_kwargs(index)
instances = kwargs.pop('instances')
try:
kwargs.update({'instance': instances[index]})
except IndexError:
pass
return kwargs
Then, when creating instances of FormSetWithInstances, you pass in a list of instances as a form kwarg:
form_set = FormSetWithInstances(form_kwargs={'instances': [...]})
I personally prefer this method because it takes advantage of existing class infrastructure instead of defining custom class members in an overridden __init__(). Also, it's in the docs.
I'm not aware of an easy way to pass a list of instances as you are trying to do. Here's a couple of options that might work depending on your use case.
You can provide initial data for the model formset. This should be a list of dictionaries, not model instances:
initial = [{'name': 'a'}, {'name': 'b'}]
formset = MyFormSet(
queryset=MyModel.objects.none(),
initial=initial,
)
Note that I have set the queryset to an empty queryset. If you didn't do this, then the formset would display existing instances, and the initial data would be used for new instances.
If you have initial values for fields that you do not wish to include in the form, then you could be able to set those values when you [save the formset].
instances = formset.save(commit=False)
names = ['a', 'b']
for instance, name in zip(instances, names):
instance.name = name
instance.save()
I have two plugins ProductSelector(parent) and SpecificationSelector(child). I want to set the child up so that when you add it to the parent the Specifications that are shown are the only ones for the product (parent). Right now it pulls in all the specifications from the table. These lines let me filter the data to get what I want.
edit: I found an error that i fixed in the code. I had the PluginBase names the same as the model. This allowed me to use ProductSelector.objects.get(cmsplugin_ptr=instance.parent) in the child to get the parent instance. I still need to figure out how to pass the filtered specification list to the "PluginAdmin Interface"
product = ProductSelector.objects.get(cmsplugin_ptr=instance.parent)
specification = Specifications.objects.filter(product_name__product_name__iexact = product.product_name)
However, I haven't figured out how to send that filtered list to the plugin admin interface.
class ProductSelectorPlugin(CMSPluginBase):
model = ProductSelector
name = "Product Selector"
render_template = "product_selector.html"
allow_children = True
child_classes = ['SpecificationSelectorPlugin']
def render(self, context, instance, placeholder):
context['instance'] = instance
return context
plugin_pool.register_plugin(ProductSelectorPlugin)
class SpecificationSelectorPlugin(CMSPluginBase):
model = SpecificationSelector
render_template = "specification_selector.html"
formfield_overrides = {models.ManyToManyField: {'widget': CheckboxSelectMultiple},}
def render(self, context, instance, placeholder):
product = ProductSelector.objects.get(cmsplugin_ptr=instance.parent)
specification = Specifications.objects.filter(product_name__product_name__iexact = product.product_name)
context['instance'] = instance
return context
plugin_pool.register_plugin(SpecificationSelectorPlugin)
models.py
class ProductSelector(CMSPlugin):
product_name = models.ForeignKey(Product, help_text = "Select the product you want to place")
new_product = models.BooleanField(blank=True)
class SpecificationSelector(CMSPlugin):
specification = models.ManyToManyField(Specifications, blank=True)
def __unicode__(self):
return unicode(self.specification)
Here is an screenshot the Django-cms plugins in the placeholder. Currently it is showing all specs in the table, but I just want it to be the specs for that particular product.
http://imgur.com/3R1LobC
Thank you in advance for the help.
CMSPluginBase inhertis from ModelAdmin which means that you can override the form rendered when adding and editing your plugin.
So you can create a ModelForm subclass like so:
class SpecificationSelectorPluginForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SpecificationSelectorPluginForm, self).__init__(*args, **kwargs)
if self.instance.parent_id:
# Assume that the parent is a product instance
parent_plugin = self.instance.parent
product = parent_plugin.get_plugin_instance()[0]
if product:
# It's possible that product is an orphan plugin.
specifications = Specifications.objects.filter(
product_name__product_name__iexact=product.product_name)
self.fields['specification'].queryset = specifications
then change your SpecificationSelectorPlugin to use this form like so:
class SpecificationSelectorPlugin(CMSPluginBase):
form = SpecificationSelectorPluginForm
The above will only work if the specification plugin is a direct child of the product plugin.
First of all: I am not able to find out the proper Title of this question.
Anyhow the question is:
I have to fill a form at template and the fields of this form are user dependent. For example you passes integer (integer is not a datatype) as a parameter to the method and it should returns like this:
fileds = forms.IntegerField()
If you pass bool then it should like this:
fields = forms.BooleanField()
So that i can use them to create my form. I tried with this code but it returns into the form of string.
Some.py file:
choices = (('bool','BooleanField()'),
('integer','IntegerField()'))
def choose_field():
option = 'bool' # Here it is hardcoded but in my app it comes from database.
for x in choices:
if x[0]==option:
type = x[1]
a = 'forms'
field = [a,type]
field = ".".join(field)
return field
When i print the field it prints 'forms.BooleanField()'. I also use this return value but it didn't work. Amy solution to this problem?
The simpliest way is to create your form class and include fields for all possible choices to it. Then write a constructor in this class and hide the fields you don't want to appear. The constructor must take a parameter indicating which fields do we need. It can be useful to store this parameter in the form and use it in clean method to correct collected data accordingly to this parameter.
class Your_form(forms.ModelForm):
field_integer = forms.IntegerField()
field_boolean = forms.BooleanField()
def __init__(self, *args, **kwargs):
option = kwargs["option"]
if option == "integer":
field_boolean.widget = field_boolean.hidden_widget()
else:
field_integer.widget = field_integer.hidden_widget()
super(Your_form, self).__init__(*args, **kwargs)
In your controller:
option = 'bool'
form = Your_form(option=option)