I am using django-translations for translating some fields of my model.
Using the Django Admin I can enter the translations and the form in the frontend shows the matching language values.
But I want to show one field for each language "similar" to the Django Admin, except I don't want to manually add the fields for non translated languages.
So if I have the languages en (default) and de I want to output title or title_en and title_de as fields.
I tried to add the translation fields myself e.g. title_de to the model, layout and meta class, but it didn't work.
How can I show all the fields for different languages simultaneously?
Model
class Category(Translatable, MPTTModel):
title = models.CharField(max_length=200)
slug = models.SlugField()
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
class Meta:
# enforcing that there can not be two categories under a parent with same slug
unique_together = ('slug', 'parent',)
class MPTTMeta:
order_insertion_by = ['title']
class TranslatableMeta:
fields = ['title', 'slug']
Form
class CategoryForm(forms.ModelForm):
use_required_attribute = False
title = forms.CharField(max_length=200)
slug = forms.SlugField(help_text=_('Will be generated automatically from the title'))
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.form_action = '.'
self.helper.form_class = 'form-horizontal form-bordered'
self.helper.label_class = 'col-lg-3'
self.helper.field_class = 'col-lg-8'
self.helper.layout = self.__get_layout()
super().__init__(*args, **kwargs)
self.fields['title'].required = True
self.fields['slug'].required = True
#staticmethod
def __get_layout():
layout = Layout(
Field('title'),
Field('title_de'),
Field('slug', readonly=''),
ButtonHolder(
Submit('submit', _('Save'))
)
)
return layout
def clean(self):
cleaned_data = super().clean()
return cleaned_data
class Meta:
model = Category
fields = ['title', 'slug']
According to the docs, you should be able to use ModelForms
Related
models.py
class InvestmentGroup(models.Model):
name = models.CharField(max_length=200, blank=True, null=True)
persons = models.ManyToManyField('Person', blank=True, related_name='investment_groups')
lead_investor = models.ForeignKey(
'Person', blank=True, null=True, on_delete=models.CASCADE, related_name='lead_investment_groups'
)
forms.py
class InvestmentGroupModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(InvestmentGroupModelForm, self).__init__(*args, **kwargs)
class Meta:
model = models.InvestmentGroup
fields = '__all__'
widgets = {
"lead_investor": autocomplete.ModelSelect2(
url="lead-investor-autocomplete",
forward=["persons"]
)
}
AutoCompleteview
class LeadInvestorAutoComplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated:
return models.Person.objects.none()
qs = models.Person.objects.all()
persons = self.forwarded.get('persons', None)
print(persons) # Output: []
if persons:
qs = qs.filter(person__in=persons)
return qs
I am getting empty values if I forward many to many field like this but works for other fields like name or foreign keys.
Is it possible to forward ManyToMany field?
You're not forwarding the persons field, which is why it seems to be empty.
If you change your form to this, it should work:
class InvestmentGroupModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(InvestmentGroupModelForm, self).__init__(*args, **kwargs)
class Meta:
model = models.InvestmentGroup
fields = '__all__'
widgets = {
"lead_investor": autocomplete.ModelSelect2(
url="lead-investor-autocomplete",
forward=["persons"]
)
}
My form triggers form_invalid when the field "category" is empty.
The weird thing is, when the view displays, the "description" field does not have the asterisk indicating it's required, unlike "name" or "enabled", for instance. Also, when I try to send the form with an empty name, it correctly displays a little yellow mark and says "This field is required", but it doesn't say that when the category is empty.
So, it seems to correctly recognize that the category is not required, it just says it's invalid after I send the form.
My form looks like this:
class ProductForm(forms.Form):
name = forms.CharField(max_length=80, required=True)
category = forms.ModelChoiceField(queryset=None, required=False, label='Categoría')
description = forms.CharField(max_length=150, required=False)
price = forms.FloatField(required=True)
image = forms.ImageField(allow_empty_file=True, required=False)
extras = forms.FileField(allow_empty_file=True, required=False)
enabled = forms.BooleanField(required=False, initial=True)
def __init__(self, user, *args, **kwargs):
self.user = user
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['name'].label = 'Nombre'
self.fields['description'].label = 'Descripción'
self.fields['price'].label = 'Precio'
self.fields['image'].label = 'Imagen'
self.fields['extras'].label = 'Extras'
categories = Category.objects.filter(store=Profile.objects.get(user=user).store)
if categories.count() == 0:
self.fields['category'].required = False
self.fields['category'].queryset = categories
self.fields['enabled'].label = 'Habilitado'
It is included to my view in this way:
class ProductCreateView(LoginRequiredMixin, CreateView):
template_name = 'products/product_form.html'
model = Product
fields = ["name", "category", "description", "price", "image", "enabled", "extra"]
success_url = reverse_lazy("orders:products")
And my model looks like this:
class Product(models.Model):
store = models.ForeignKey(Store, related_name="products", on_delete=models.PROTECT)
name = models.CharField(max_length=100, verbose_name="Nombre")
description = models.CharField(max_length=500, verbose_name="Descripción", null=True)
price = models.FloatField(verbose_name="Precio")
image = models.ImageField(upload_to="media/", verbose_name="Imagen", null=True, blank=True)
enabled = models.BooleanField(default=False, verbose_name="Habilitado")
extra = models.FileField(upload_to="media/files/", verbose_name="Extras", max_length=254, null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.PROTECT, null=True)
detail_enabled = models.BooleanField(default=False)
You never use the ModelForm you constructed. Django will create its own since nowhere you specify form_class=… [Django-doc] in your CreateView. But that will not be sufficient, since Django will not pass a user by default. You will need to override the .get_form_kwargs(…) [Django-doc] as well to pass the user.
You also should make the ProductForm a ModelForm, since otherwise it has no .save() method:
class ProductForm(forms.ModelForm):
# …
class Meta:
model = Product
fields = ['name', 'category', 'description', 'price', 'image', 'enabled', 'extra']
In your view you thus specify the form_class, and override the get_form_kwargs, to inject the user in the ModelForm constructor:
class ProductCreateView(LoginRequiredMixin, CreateView):
template_name = 'products/product_form.html'
model = Product
form_class = ProductForm
success_url = reverse_lazy('orders:products')
def get_form_kwargs(self, *args, **kwargs):
fk = super().get_form_kwargs(*args, **kwargs)
fk['user'] = self.request.user
return fk
My goal is to loop through all form fields and to assign certain classes to them like this:
class ContactForm(forms.Form):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control input-sm plain'
if field.required == True:
field.widget.attrs['required'] = ''
class Meta:
model = Contact
fields = '__all__'
The issue with this code is that self.fields.items() seems to be empty (and as a result I never get into the for-loop).
My guess is that the issue arose either because of my upgrade from Django 1.9 and python 2 to Django 1.10 and python 3, or because of custom manager present in the definition of the underlying model.
Could anyone share expertise on this?
class Contact(BaseMixin, DeleteMixin):
provider_account = models.ForeignKey(ProviderAccount, models.DO_NOTHING)
client_id = models.IntegerField()
name = models.CharField(max_length=300)
profile_photo_url = models.CharField(max_length=100, default = 'no_image.jpg')
event_type_id = models.IntegerField(EventType.choices(), blank=True, null=True)
is_satisfied = models.NullBooleanField()
objects = CustomQuerySetManager()
class Meta:
managed = False
db_table = 'contact'
class QuerySet(QuerySet):
#....
Your form is a standard form, not a model form; the Meta class is ignored and the only fields are those you define yourself.
Your form should inherit from forms.ModelForm for this to work.
I have a model where a field references a foreign key from another model as:
class DummyModel(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=150)
image_type = models.ForeignKey(ImageTypeModel) # Foreign key
class Meta:
db_table = "dummy"
The parent model is also simple:
class ImageTypeModel(models.Model):
name = models.CharField(max_length=100)
dims = models.IntegerField()
class Meta:
db_table = "imagetypes"
Now, I attempt to render a record in a form and for that purpose I am using django-crispy-forms. So, I have:
class DummyForm(ModelForm):
class Meta:
model = DummyModel
fields = ['name', 'description', 'image_type']
def __init__(self, *args, **kwargs):
super(DummyForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-sm-2'
self.helper.field_class = 'col-sm-10'
#self.helper.form_tag = False
self.helper.layout = Layout(
Field('name'),
Field('description'),
Field('image_type'))
The image_type field renders as a drop-down list which is perfect but instead of the name of the image types, the entries are all labelled ImageTypeModel. Is there a mechanism so that I can display the corresponding name from the ImageTypeModel record but when the form is saved it saves the primary key rather than the name.
You should implement the __unicode__ (python 2) or __str__ (python 3) method inside the model.
Like this:
class ImageTypeModel(models.Model):
name = models.CharField(max_length=100)
dims = models.IntegerField()
class Meta:
db_table = "imagetypes"
# For Python 2
def __unicode__(self):
return self.name
# For Python 3
def __str__(self):
return self.name
I want to let users to choose their countries. I have 2 models = Countries with some figures and CountriesTranslations. I am trying to make tuple with country (because user has FK to this model) and its translation. In front-end I see dropdown list of countries, but when I try to save the form, I see
error: Exception Value: Cannot assign "'AF'": "UserProfile.country" must be a "Countries" instance.
Error happens at the line if user_profile_form.is_valid():
# admindivisions.models
class Countries(models.Model):
osm_id = models.IntegerField(db_index=True, null=True)
status = models.IntegerField()
population = models.IntegerField(null=True)
iso3166_1 = models.CharField(max_length=2, blank=True)
iso3166_1_a2 = models.CharField(max_length=2, blank=True)
iso3166_1_a3 = models.CharField(max_length=3, blank=True)
class Meta:
db_table = 'admindivisions_countries'
verbose_name = 'Country'
verbose_name_plural = 'Countries'
class CountriesTranslations(models.Model):
common_name = models.CharField(max_length=81, blank=True, db_index=True)
formal_name = models.CharField(max_length=100, blank=True)
country = models.ForeignKey(Countries, on_delete=models.CASCADE, verbose_name='Details of Country')
lang_group = models.ForeignKey(LanguagesGroups, on_delete=models.CASCADE, verbose_name='Language of Country',
null=True)
class Meta:
db_table = 'admindivisions_countries_translations'
verbose_name = 'Country Translation'
verbose_name_plural = 'Countries Translations'
# profiles.forms
class UserProfileForm(forms.ModelForm):
# PREPARE CHOICES
country_choices = ()
lang_group = Languages.objects.get(iso_code='en').group
for country in Countries.objects.filter(status=1):
eng_name = country.countriestranslations_set.filter(lang_group=lang_group).first()
if eng_name:
country_choices += ((country, eng_name.common_name),)
country_choices = sorted(country_choices, key=lambda tup: tup[1])
country = forms.ChoiceField(choices=country_choices, required=False)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
# profiles.views
def profile_settings(request):
if request.method == 'POST':
user_profile_form = UserProfileForm(request.POST, instance=request.user)
if user_profile_form.is_valid():
user_profile_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_profile_form = UserProfileForm(instance=request.user)
return render(request, 'profiles/profiles_settings.html', {
'user_profile_form': user_profile_form,
})
As I understand, country from ((country, eng_name.common_name),) is converted to str. What is the right way to keep country instance in the form? or if I am doing it in the wrong way, what way is correct?
EDITED:
As a possible solution is to use ModelChoiceField with overriding label_from_instance as shown below:
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang='en', *args, **kwargs):
super(CountriesChoiceField, self).__init__(*args, **kwargs)
self.user_lang = user_lang
def label_from_instance(self, obj):
return obj.countriestranslations_set.get(lang_group=self.user_lang)
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(
queryset=Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name'),
widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
but this solution produces too much queries because of the query in label_from_instance and page loads too slowly. Would appreciate any advice.
You probably want to use forms.ModelChoiceField instead of the forms.ChoiceField for your dropdown list.
The ModelChoiceField builds based on a QuerySet and preserves the model instance.
Seems to be solved.
The version below produces 7 queries in 29.45ms vs 73 queries in 92.52ms in the EDITED above. I think it is possible to make it even faster if to set unique_together for some fields.
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang, *args, **kwargs):
queryset = Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name')
super(CountriesChoiceField, self).__init__(queryset, *args, **kwargs)
self.translations = OrderedDict()
for country in queryset:
name = country.countriestranslations_set.get(lang_group=user_lang).common_name
self.translations[country] = name
def label_from_instance(self, obj):
return self.translations[obj]
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
So, now it is possible to have choices based on two (2) models with a good speed. Also the DRY principle is applied, so if there is a need to use choices multiple times in different forms - no problem.