Django modelform: exclude fields by regex - django-forms

I have a "Villa" Model with lots of descriptive TextFields. For each TextField, I have a copy which will be the Russian translation of the original field, which I'm naming by appending "_ru", for example "long_description" and "long_description_ru". I would like to exclude all the "_ru" fields from my ModelForm, which I thought I would be able to do like this:
class VillaForm(ModelForm):
class Meta:
model = Villa
exclude = []
for field_name in Villa.__dict__:
print field_name
if field_name.endswith("_ru"):
exclude.append(field_name)
However, Villa.__dict__ does not contain the TextFields - even though they get rendered by the ModelForm. Am I being very stupid here?

I see it's been a while since you asked this but I have an answer for you. I think this might be easier to do in the __init__ function:
class VillaForm(ModelForm):
class Meta:
model = Villa
def __init__(self, *args, **kwargs):
super(VillaForm, self).__init__(*args, **kwargs)
for field_name in self.fields.keys():
if field_name.endswith("_ru"):
del self.fields[field_name]
The code is not entirely tested but I do think it's easier to do in the __init__ vs. in the Meta definition.

Related

Decent way to add a validator for DRF Field class child

I have a custom DRF field element that looks like this:
class TimestampField(serializers.DateTimeField):
def __init__(self, allow_future=True, *args, **kwargs):
self.allow_future = allow_future
def some_sort_of_validator(...): # Don't know how to do that
if not self.allow_future:
if value > timezone.now():
raise ValidationError('...')
Basically, I want to do some custom validation for that field elements. For example, I want to assure that future dates are prohibited. Looks like I need to add something that is refered to as validator in the docs. And I wonder how to do that correctly, so that not to kill native validators. I found nothing regarding this neither in the DRF doc, nor in SO.
There is an article in the docs about writing validators and a section about writing custom validators.
in your case, something like this should work
class TimestampValidator:
def __init__(self, allow_future):
self.allow_future = allow_future
def __call__(self, value):
if not self.allow_future:
if value > timestamp.now():
raise ValidationError('...')
and to use it in your actual serializer
class MySerializer((serializers.Serializer):
timestamp = serializers.DateTimeField(validators=[TimestampValidator(allow_future=True)])
# .. the rest of your serializer goes here
you can also check the code for the built-in validators to see how they are done

Django REST field level costume permissions in a ViewSet

I'd like to validate whether users have permission to update a "shared_with" field , right now I'm doing it with a direct call to .has_perm, but this seems to not scalable, anyone know what's the best practice in this case?
views.py:
class MyViewSet(viewsets.ModelViewSet):
#staticmethod
def _check_sharing_permission(request: Request):
if request.data.get('shared_with') and not
request.user.has_perm(CAN_SHARE_PERMISSION_CODENAME):
raise PermissionDenied('you shall not pass!')
def update(self, request, *args, **kwargs):
self._check_sharing_permission(request)
return super().update(request, *args, **kwargs)
Found the answer here:
How do I access the properties of a many-to-many "through" table from a django template?
No need to use has_perm
The intermediate table can be referenced in such cases

How to add all remaining model fields to a django crispy form layout?

I'm using django-crispy-forms, and I have a form with many fields. I only want to customize a few of those fields, for example:
class ExampleForm(forms.ModelForm):
model = ExampleModel
fields = "__all__" # tons of fields
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Field('readonlyfield', readonly=True),
# Add rest of fields here without explicitly typing them all out
)
If I render this form, it will only have the one field. How do I add the rest of them with their default layout values/settings?
I think the best way to do this would be to use the get_fields method of Django's Option class, which is available on your model's ._meta property, combined with star-unpacking. Like so:
class ExampleForm(forms.ModelForm):
model = ExampleModel
fields = "__all__" # tons of fields
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
# This list contains any fields that you are explicitly typing out / adding to the Layout
manually_rendered_fields = ['readonlyfield',]
# Next, create an array of all of the field name BESIDES the manually_rendered_fields
all_other_fields = [f.name for f in self.model._meta.get_fields() if f not in manually_rendered_fields]
self.helper.layout = Layout(
Field('readonlyfield', readonly=True),
# Add rest of fields here without explicitly typing them all out
*all_other_fields,
# if you needed to provide custom kwargs for the other fields, you could do something like this instead:
*[Field(f, kwarg1=True, kwarg2=False) for f in all_other_fields],
)

Django Forms - is it advisable to change user submitted data in the clean method

I have the following code in my Django project:
def my_view(request):
form = MyForm(request.POST):
if form.is_valid():
instance = form.save(commit = False)
instance.some_field = 'foo'
form.save()
#...
The question is, is it advisable to rewrite this the following way:
forms.py
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# ...
def clean(self):
form.some_field = 'foo'
I wonder, if clean method should be used exclusively for data validation, or I could also perform here some business logic stuff, thus making my views more concise and devoid of business logic details.
One of possible advantages of making the assignment in the clean method is that sometimes data validation could be dependent on some_field.
Have you considered putting it in .save?

How can a ChoiceField.choices callable know what choices to return?

In Django 1.8, the ChoiceField's choices argument can accept a callable:
def get_choices():
return [(1, "one"), (2, "two")]
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_choices)
In the above example, get_choices() always returns the same choices. However, being able to assign a callable to choices does not make much sense unless that callable knows something like, say, an object id, each time it is called. How can I pass such a thing to it?
You can't do it in the form declaration because the CallableChoiceIterator calls the function without arguments that he gets from here.
Doing in the __init__ Form method is easier than creating your own ChoiceField I guess. Here is what I suggest:
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=())
def __init__(self, *args, **kwargs):
# Let's pass the object id as a form kwarg
self.object_id = kwargs.pop('object_id')
# django metaclass magic to construct fields
super().__init__(*args, **kwargs)
# Now you can get your choices based on that object id
self.fields['my_choice_field'].choices = your_get_choices_function(self.object_id)
That supposes that you have some Class Based View that looks that has a method like this :
class MyFormView(FormView):
# ...
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['object_id'] = 'YOUR_OBJECT_ID_HERE'
return kwargs
# ...
P.S : The super() function call supposes you are using python 3
The reason it's possible to set a callable like that is to avoid situations where you're using models before they're ready.
forms.py
class Foo(ModelForm):
choice_field = ChoiceField(choices=[
user.username.lower() for user in User.objects.all()
])
Were forms.py imported before models were ready, (which it probably is because views.py generally likes to import it, and urls.py generally likes to import that, and urls.py is imported by the startup machinery), it will raise an exception due to trying to do ORM stuff before all the apps are imported.
The correct way is to use a callable like so:
def lower_case_usernames():
return [user.username.lower() for user in User.objects.all()]
class Foo(ModelForm):
choice_field = ChoiceField(choices=lower_case_usernames)
This also has the benefit of being able to change without restarting the server.

Resources