Do you have a hint why captcha is not showing?
from captcha.fields import CaptchaField
class ResponseForm(models.ModelForm):
captcha = CaptchaField() # added this according to documentation
class Meta:
model = Response
fields = ['comments']
def __init__(self, *args, **kwargs):
[...]
def save(self, commit=True):
[...]
It works here:
from captcha.fields import CaptchaField
class UserCreationForm(UserCreationFormBase):
email = forms.EmailField(
label='Email',
error_messages={'invalid': 'Check email address!'})
captcha = CaptchaField() # works fine here
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
def get_credentials(self):
[...]
Is it because of functions def __init__ and/or def save?
The behavior is that no captcha field is shown and that the form does nothing on hitting submit. Without captcha in the code it's working fine.
Update
No error message. It seems page loads again.
Console output: POST /umfragen/testumfrage-2/ HTTP/1.1 200 2661
Full def __init__:
# expects a survey object passed in initially
survey = kwargs.pop('survey')
self.survey = survey
super(ResponseForm, self).__init__(*args, **kwargs)
self.uuid = random_uuid = uuid.uuid4().hex
# adds a field for each survey question,
# corresponding to the question type
data = kwargs.get('data')
for q in survey.questions():
if q.question_type == Question.TEXT:
self.fields['question_%d' % q.pk] = forms.CharField(
label=q.question_text,
widget=forms.Textarea)
elif q.question_type == Question.RADIO:
# calls function get_choices from models.py
question_choices = q.get_choices()
self.fields['question_%d' % q.pk] = forms.ChoiceField(
label=q.question_text,
widget=forms.RadioSelect,
choices=question_choices)
elif q.question_type == Question.SELECT:
question_choices = q.get_choices()
question_choices = tuple([('', '- - - - -')]) + question_choices
self.fields['question_%d' % q.pk] = forms.ChoiceField(
label=q.question_text,
widget=forms.Select,
choices=question_choices)
elif q.question_type == Question.SELECT_MULTIPLE:
question_choices = q.get_choices()
self.fields['question_%d' % q.pk] = forms.MultipleChoiceField(
label=q.question_text,
widget=forms.CheckboxSelectMultiple,
choices=question_choices)
# adds category as a data attribute and allows sorting the questions by category
if q.category:
classes = self.fields['question_%d' % q.pk].widget.attrs.get('class')
if classes:
self.fields['question_%d' % q.pk].widget.attrs['class'] = classes + ('cat_%s' % q.category.name)
else:
self.fields['question_%d' % q.pk].widget.attrs['class'] = ('cat_%s' % q.category.name)
self.fields['question_%d' % q.pk].widget.attrs['category'] = q.category.name
# initializes the form field with values from a POST request
if data:
self.fields['question_%d' % q.pk].initial = data.get('question_%d' % q.pk)
Related
I have a working formset.
My goal is to define a field choices from other model, based on pk sended on url. It's almost working, but init method is executed twyce and cleans kwargs.
Model:
class Delito(models.Model):
numero = models.ForeignKey(Expediente, on_delete=models.CASCADE, blank = False)
delito = models.ForeignKey(CatalogoDelitos, on_delete=models.CASCADE, blank = False)
imputado = models.ForeignKey(Imputado, on_delete=models.CASCADE, blank = False)
categoria = models.CharField('Categoria', max_length = 20, blank = False, choices = CATDEL_CHOICES)
My URL:
path('crear_delito/<int:pk>', login_required(CrearDelito.as_view()), name ='crear_delito'),
Forms.py:
class CrearDelitoForm(forms.ModelForm):
class Meta:
model = Delito
exclude = ()
def __init__(self, numero_pk = None, *args, **kwargs):
super(CrearDelitoForm, self).__init__(*args, **kwargs)
self.fields["imputado"].queryset = Imputado.objects.filter(numero_id = numero_pk)
DelitoFormset = inlineformset_factory(
Expediente,
Delito,
form=CrearDelitoForm,
extra=1,
can_delete=True,
fields=('imputado', 'delito', 'categoria'),
}
)
Views.py:
class CrearDelito(CreateView):
model = Delito
form_class = CrearDelitoForm
template_name = 'crear_delito.html'
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['formset'] = DelitoFormset()
context['expedientes'] = Expediente.objects.filter(id = self.kwargs['pk'])
return context
def get_form_kwargs(self, **kwargs):
kwargs['numero_pk'] = self.kwargs['pk']
return kwargs
If I print queryset, it works at first time, but is passed twice cleaning "numero_pk" value:
System check identified no issues (0 silenced).
June 08, 2020 - 11:33:57
Django version 2.2.12, using settings 'red.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
<QuerySet [<Imputado: Martín Ric>, <Imputado: Marcos Gomez>]>
<QuerySet []>
If I put the value as string it works fine, eg.:
def __init__(self, numero_pk = None, *args, **kwargs):
super(CrearDelitoForm, self).__init__(*args, **kwargs)
self.fields["imputado"].queryset = Imputado.objects.filter(numero_id = '6')
enter image description here
I publish the solution given in other question, for :
class CrearDelito(CreateView):
model = Delito
form_class = CrearDelitoForm
template_name = 'crear_delito.html'
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['formset'] = DelitoFormset()
context['expedientes'] = Expediente.objects.filter(id = self.kwargs['pk'])
return context
def get_form_kwargs(self, **kwargs):
kwargs['numero_pk'] = self.kwargs['pk']
return kwargs here
I managed to get the below code limited to 1 user review per restaurant and it works great using the class meta "unique together"
class UserReview(models.Model):
# Defining the possible grades
Grade_1 = 1
Grade_2 = 2
Grade_3 = 3
Grade_4 = 4
Grade_5 = 5
# All those grades will sit under Review_Grade to appear in choices
Review_Grade = (
(1, '1 - Not satisfied'),
(2, '2 - Almost satisfied'),
(3, '3 - Satisfied'),
(4, '4 - Very satisfied'),
(5, '5 - Exceptionally satisfied')
)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
user_review_grade = models.IntegerField(default=None, choices=Review_Grade) # default=None pour eviter d'avoir un bouton vide sur ma template
user_review_comment = models.CharField(max_length=1500)
posted_by = models.ForeignKey(User, on_delete=models.DO_NOTHING)
class Meta:
unique_together = ['restaurant', 'posted_by']
I now realise that I need to update my view so that is this constraint is failed I am taken to an error page, but I can't find how, any guidance would be appreciated
View:
class Reviewing (LoginRequiredMixin, CreateView):
template_name = 'restaurants/reviewing.html'
form_class = UserReviewForm
# Get the initial information needed for the form to function: restaurant field
def get_initial(self, *args, **kwargs):
initial = super(Reviewing, self).get_initial(**kwargs)
initial['restaurant'] = self.kwargs['restaurant_id']
return initial
# Post the data into the DB
def post(self, request, restaurant_id, *args, **kwargs):
form = UserReviewForm(request.POST)
restaurant = get_object_or_404(Restaurant, pk=restaurant_id)
if form.is_valid():
review = form.save(commit=False)
form.instance.posted_by = self.request.user
print(review) # Print so I can see in cmd prompt that something posts as it should
review.save()
# this return below need reverse_lazy in order to be loaded once all the urls are loaded
return HttpResponseRedirect(reverse_lazy('restaurants:details', args=[restaurant.id]))
return render(request, 'restaurants/oops.html')
Form:
# Form for user reviews per restaurant
class UserReviewForm(forms.ModelForm):
class Meta:
model = UserReview
# restaurant = forms.ModelChoiceField(queryset=Restaurant.objects.filter(pk=id))
fields = [
'restaurant',
'user_review_grade',
'user_review_comment'
]
widgets = {
'restaurant': forms.HiddenInput,
'user_review_grade': forms.RadioSelect,
'user_review_comment': forms.Textarea
}
labels = {
'user_review_grade': 'Chose a satisfaction level:',
'user_review_comment': 'And write your comments:'
}
In case the form is not valid, you can redirect to an error page:
from django.db import IntegrityError
class Reviewing (LoginRequiredMixin, CreateView):
template_name = 'restaurants/reviewing.html'
form_class = UserReviewForm
# Get the initial information needed for the form to function: restaurant field
def get_initial(self, *args, **kwargs):
initial = super(Reviewing, self).get_initial(**kwargs)
initial['restaurant'] = self.kwargs['restaurant_id']
return initial
# Post the data into the DB
def post(self, request, restaurant_id, *args, **kwargs):
form = UserReviewForm(request.POST)
restaurant = get_object_or_404(Restaurant, pk=restaurant_id)
if form.is_valid():
review = form.save(commit=False)
form.instance.posted_by = self.request.user
print(review) # Print so I can see in cmd prompt that something posts as it should
try:
review.save()
except IntegrityError:
return redirect('name-of-some-view')
# this return below need reverse_lazy in order to be loaded once all the urls are loaded
return redirect('restaurants:details', restaurant.id)
return redirect('name-of-some-view')
That being said, you doo too much in your view. Django's CreateView is designed to remove most of the boilerplate code for you.
You thus can implement this as:
class Reviewing (LoginRequiredMixin, CreateView):
template_name = 'restaurants/reviewing.html'
form_class = UserReviewForm
# Get the initial information needed for the form to function: restaurant field
def get_initial(self, *args, **kwargs):
initial = super(Reviewing, self).get_initial(**kwargs)
initial['restaurant'] = self.kwargs['restaurant_id']
return initial
def form_valid(self, form):
form.instance = self.request.user
return super().form_valid(form)
def get_success_url(self, **kwargs):
return reverse('restaurants:details', args=[self.kwargs['restaurant_id']])
def form_invalid(self, form):
return redirect('name-of-some-view')
I try to implement validators for Django project
I've defined a file validators.py in myapp
I add validators=[validate_test] for my ran_num field
but got an error
I have another question: is it possible to control field BEFORE form submission?
validators.py
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
def validate_test(self, value):
if value == 'PPP': # just test
raise ValidationError(
_("Test"),
code='Test',
params={},
)
forms.py
from .validators import validate_date
class EditForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs):
super(EditForm, self).__init__(*args, **kwargs)
self.request = request
self.fields["asp_ent_loc"] = forms.ChoiceField(label = _("Site concerned by the operation"), widget=forms.Select, choices=SITE_CONCERNE)
self.fields["med_num"] = forms.CharField(label = _("Trial bacth number"), required=True,validators=[validate_test] )
self.fields["asp_ent_dat"] = forms.DateField(
label = _("Entry date"),
required = True,
initial = datetime.datetime.now(),
)
self.fields["asp_ent_pro_pay"] = forms.ChoiceField(label = _("Country of treatment origin in case of entry"), widget=forms.Select, choices=PAYS)
self.fields["asp_ent_pro_sit"] = forms.ChoiceField(label = _("Processing source site in case of entry"), widget=forms.Select, choices=SITE_PROVENANCE)
def clean(self):
cleaned_data = super(EditForm, self).clean()
class Meta:
model = Entree
fields = ('asp_ent_loc','med_num','asp_ent_dat','asp_ent_pro_pay','asp_ent_pro_sit',)
def clean_med_num(self):
data = self.cleaned_data['med_num']
if len(data) != 3:
raise forms.ValidationError(_("Error on Batch number format (3 letters)"))
if not Medicament.objects.filter(med_num = data).exists():
raise ValidationError(_('Batch number does not exist'))
return data
def clean_asp_ent_dat(self):
data = self.cleaned_data['asp_ent_dat']
entrydate = datetime.datetime.strptime(str(data), "%Y-%m-%d")
currentdate = datetime.datetime.now()
if entrydate > currentdate:
raise forms.ValidationError(_("Please control entry date"))
return data
Your Validation function is not a model's method which means it doesn't accept the self. Your Validation func only accept value argument. In your scenario-
def validate_test(value): # remove self from here
if value == 'PPP':
raise ValidationError(
_("Test"),
code='Test',
params={},
)
I'm trying to limit the fields list in serializers based on user permissions. I have a generic routine that does it for all serializers. It's working on the parent serializer, but not on a nested serializer.
I have a client model, and a client profile (referred to as "contacts") as shown below. The client profile model is an extension of the user model (one-to-one relationship).
class Client(AddressPhoneModelMixin, DateFieldsModelMixin, models.Model):
name = models.CharField(max_length=100)
status = models.CharField(max_length=25)
class Meta:
permissions = (
# Object-level
('view_all_clients', 'Can view all clients'),
('change_all_clients', 'Can change all clients'),
# Field-level
('view_client_id', 'Can view client ID'),
('view_client_name', 'Can view client name'),
...others omitted...
)
class ClientProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
blank=True,
null=True,
)
client = models.ForeignKey(
Client,
on_delete=models.PROTECT,
related_name='contacts',
)
receive_invoices = models.BooleanField(default=False)
My object-level permission logic is in the list view:
class ClientList(ListAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ClientSerializer
def get_queryset(self):
user = self.request.user
queryset = None
if user.has_perm('view_client') or user.has_perm('clients.view_all_clients'):
queryset = Client.objects.all().exclude(status__in=['deleted', 'archived'])
if user.has_perm('view_client'): # View only "assigned" clients
if user.type == 'client':
# See if user is a "contact".
queryset = queryset.distinct().filter(contacts__user=self.request.user)
else:
# See if user is assigned to projects for the client(s).
queryset = queryset.distinct().filter(projects__project_users__user=self.request.user)
if queryset is None:
raise PermissionDenied('You do not have permission to view clients.')
return self.get_serializer_class().setup_eager_loading(queryset)
Removing fields from the serializer "fields" property is done in the serializer __init__ method (from examples I found here in SO):
class ClientContactsSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='clients:clientprofile-detail')
user = UserSerializer()
class Meta:
model = ClientProfile
fields = (
'url',
'receive_invoices',
'user',
)
def __init__(self, *args, **kwargs):
super(ClientContactsSerializer, self).__init__(*args, **kwargs)
check_field_permissions(self, 'view')
class ClientSerializer(AddressPhoneSerializerMixin, serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='clients:client-detail')
contacts = ClientContactsSerializer(many=True, read_only=True)
projects = ClientProjectsSerializer(many=True, read_only=True)
class Meta:
model = Client
fields = (
'url',
'id',
'name',
...omitted for brevity...
'contacts',
'projects',
)
def __init__(self, *args, **kwargs):
super(ClientSerializer, self).__init__(*args, **kwargs)
check_field_permissions(self, 'view')
#staticmethod
def setup_eager_loading(queryset):
queryset = queryset.select_related('country')
return queryset.prefetch_related('contacts', 'contacts__user', 'contacts__user__country', 'projects')
And, finally, here's the check_field_permissions function:
def check_field_permissions(serializer, action='view'):
request = serializer.context.get('request', None)
fields = serializer.get_fields()
model = serializer.Meta.model
app_name = model._meta.app_label
model_name = model._meta.model_name
if request is not None and app_name is not None and model_name is not None:
user = request.user
for field_name in fields:
if hasattr(serializer.fields[field_name], 'child'):
continue
if not user.has_perm(app_name + '.' + action + '_' + model_name + '_' + field_name):
serializer.fields.pop(field_name)
Stepping through in debug on a page-load of the Client list, I can see that the above function is invoked first for clientprofile, and request is None. The second time, it is invoked for client, and request is a valid request object.
First question, is __init__ the correct place for limiting the list of fields to be serialized?
Second, how to get the request object in the nested serializer (clientprofile)?
After reading a post by Tom Christie, who is an undisputed authority on DRF, I was able to solve my issue. He pointed out that each nested serializer does, in fact, have the context object (and the request, and the user). You just have to deal with nested serializers in the parent __init__ - not their own __init__.
Here's my revised check_field_permissions() function:
def check_field_permissions(serializer, action='view'):
request = serializer.context.get('request', None)
fields = serializer.get_fields()
model = serializer.Meta.model
app_name = model._meta.app_label
model_name = model._meta.model_name
if request is not None and app_name is not None and model_name is not None:
user = request.user
extra_fields = []
for field_name in fields:
if field_name == 'url':
extra_fields.append(field_name)
continue
if hasattr(serializer.fields[field_name], 'child'):
check_field_permissions(serializer.fields[field_name].child, action)
extra_fields.append(field_name)
continue
if not user.has_perm(app_name + '.' + action + '_' + model_name + '_' + field_name):
serializer.fields.pop(field_name)
# If only "url" and child fields remain, remove all fields.
if len(serializer.fields) == len(extra_fields):
for field_name in extra_fields:
serializer.fields.pop(field_name)
It is now recursive. If it hits a field with a "child" attribute, it knows that's a nested serializer field. It calls itself with that child serializer passed as an arg.
The other change is that __init__ was removed from ClientContactsSerializer, because it doesn't need to call check_field_permissions().
I'm trying to run a validation where a user can't enter the same name_field twice but other users entering the same name will not interfere.
I tried using "unique_together = (("username","name_field"))" but when a user enters the same value twice the server generates an integrity error as opposed to rendering a warning message next to the form field.
then I tried overriding the clean() method in my model, Which runs fine if I only check "field_name" like so:
def clean(self):
existing = self.__class__.objects.filter(
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
But I am running into trouble when checking the username value, for instance:
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
I'm guessing due to it not being an actual field in the form its not present during the call to clean(). So my question is am I doing the validation correctly for this kind of problem? And how can I pass or where can I find the value for the current user from within a models clean method (in a safe way hopefully without adding fields to my form)?
views.py
def add_stuff(request):
if request.user.is_authenticated():
form = addStuff(request.POST or None)
if request.method == 'POST':
if form.is_valid():
sub_form = form.save(commit=False)
sub_form.username = request.user
sub_form.save()
return redirect('../somewhere_else/')
forms.py
class addStuff(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(addStuff, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
'name_field',
'type_field',
ButtonHolder(
Submit('Save', 'Save')
),
)
class Meta:
model = UserStuff
fields = ('name_field',
'type_field',
)
models.py
class UserStuff(models.Model):
username = models.ForeignKey(User)
name_field = models.CharField(max_length=24, blank=False,null=False)
type_field = models.CharField(max_length=24, blank=True,null=True)
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
def __unicode__(self):
return "%s For User: \"%s\" " % (self.name_field, self.username)
class Meta:
managed = True
db_table = 'my_db_table'
unique_together = (("username","name_field"))
Thanks for any insight!
I now am running the clean override from the form instead of the model (as recommended by Daniel). This has solved a bunch of issues and I now have a working concept:
models.py
class UserStuff(models.Model):
username = models.ForeignKey(User)
name_field = models.CharField(max_length=24, blank=False,null=False)
type_field = models.CharField(max_length=24, blank=True,null=True)
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
def __unicode__(self):
return "%s For User: \"%s\" " % (self.name_field, self.username)
class Meta:
managed = True
db_table = 'my_db_table'
forms.py
class addStuff(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(addStuff, self).__init__(*args, **kwargs)
initial = kwargs.pop('initial')
self.username = initial['user']
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
'name_field',
'type_field',
ButtonHolder(
Submit('Save', 'Save')
),
)
def clean(self):
cleaned_data = super(addStuff, self).clean()
name_field = self.cleaned_data['name_field']
obj = UserStuff.objects.filter(username_id=self.username.id,
name_field=name_field,
)
if len(obj) > 0:
raise ValidationError({'name_field':
"This name already exists!" } )
return cleaned_data
class Meta:
model = UserStuff
fields = ('name_field',
'type_field',
)
views.py
def add_stuff(request):
if request.user.is_authenticated():
form = addStuff(request.POST or None,
initial={'user':request.user})
if request.method == 'POST':
if form.is_valid():
sub_form = form.save(commit=False)
sub_form.username = request.user
sub_form.save()
return redirect('../somewhere_else/')
best of luck!