How to find out current logged-in user in widget in Django Wagtail - django-forms

I have been looking for a way to find out who the current logged-in user is in Django Wagtail so that I could create a widget to render a base setting field to be editable/non-editable. I was able to get some basic logic working but couldn't figure out how to find who the current logged-in user is. Can someone help me find out what's the best and most secure way to go about this?
models.py
#register_setting
class AdminSetting(BaseSetting):
...
permitted_retries = models.IntegerField(null=False, default=10)
panels =[
FieldPanel('permitted_retries', widget=PermittedRetriesWidget())
]
base_form_class = AdminSettingForm
admin_setting_forms.py
class AdminSettingForm(WagtailAdminPageForm):
def __init__(self, user=None, *args, **kwargs):
self.user = user
super(AdminSettingForm, self).__init__(*args, **kwargs)
self.fields['permitted_retries'].widget.user = 'me' # This goes to widget
def clean(self):
cleaned_data = super().clean()
return cleaned_data
def save(self, commit=True):
page = super().save(commit=False)
if commit:
page.save()
return page
widgets.py
class PermittedRetriesWidget(forms.Widget):
...
def render(self, name, value, attrs=None, renderer=None):
if self.user.is_superuser:
return format_html(f'<input type="hidden" name="{name}" value="{value}" id="id_{name}">')
else:
output = f'<div style="padding: 1.2em;">{value}</div>'
input = f'<input type="hidden" name="{name}" value="{value}" id="id_{name}">'
return format_html(output + input)

I finally figured it out by using ThreadLocals, I think it's the easier and faster way.
middleware.py
from django.utils.deprecation import MiddlewareMixin
import threading
_thread_locals = threading.local()
def get_current_request():
return getattr(_thread_locals, 'request', None)
class ThreadLocals(MiddlewareMixin):
"""
Middleware that gets various objects from the
request object and saves them in thread local storage.
"""
def process_request(self, request):
_thread_locals.request = request
admin_settings_form.py
from app.contrib.middleware import get_current_request
class AdminSettingForm(WagtailAdminPageForm):
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
current_user = current_request.user
is_superuser = current_user.is_superuser or False
self.fields['permitted_retries'].widget.is_superuser = is_superuser
def clean(self):
cleaned_data = super().clean()
return cleaned_data
def save(self, commit=True):
page = super().save(commit=False)
if commit:
page.save()
return page
widgets.py
class PermittedRetriesWidget(forms.Widget):
...
def render(self, name, value, attrs=None, renderer=None):
is_superuser = self.is_superuser
if is_superuser:
# Shows the input as it is
return format_html(f'<input type="number" name="{name}" value="{value}" id="id_{name}">')
else:
# Hide the input and show the value as uneditable
output = f'<div style="padding: 1.2em;">{value}</div>'
input = f'<input type="hidden" name="{name}" value="{value}" id="id_{name}">'
return format_html(output + input)

I have found another way to do this! This is probably better than ThreadLocal as I have been hearing so much bad things about it. I hope this will help someone out in the future.
panels.py
class RequestBoundFieldPanel(FieldPanel):
def on_request_bound(self):
if self.widget:
setattr(self.widget, 'request', self.request)
models.py
from .panels import RequestBoundFieldPanel(FieldPanel):
#register_setting
class AdminSetting(BaseSetting):
...
permitted_retries = models.IntegerField(null=False, default=10)
panels =[
RequestBoundFieldPanel('permitted_retries', widget=PermittedRetriesWidget())
]
base_form_class = AdminSettingForm
admin_settings_form.py
class AdminSettingForm(WagtailAdminPageForm):
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
is_superuser = self.fields['permitted_retries'].widget.request.user.is_superuser or False
def clean(self):
cleaned_data = super().clean()
return cleaned_data
def save(self, commit=True):
page = super().save(commit=False)
if commit:
page.save()
return page
widgets.py
class PermittedRetriesWidget(forms.Widget):
...
def __init__(self, attrs=None, *args, **kwargs):
self.request = kwargs.pop('request', None)
super().__init__(*args, **kwargs)
def render(self, name, value, attrs=None, renderer=None):
is_superuser = self.request.user.is_superuser or False
if is_superuser:
# Shows the input as it is
return format_html(f'<input type="number" name="{name}" value="{value}" id="id_{name}">')
else:
# Hide the input and show the value as uneditable
output = f'<div style="padding: 1.2em;">{value}</div>'
input = f'<input type="hidden" name="{name}" value="{value}" id="id_{name}">'
return format_html(output + input)

Related

How can I delete cache_page in Django?

I cached the view using cache_page in Django. But I want to clear the cache_page when a model in my view is updated.
views.py
#method_decorator(cache_page(168 * 3600,), name="dispatch")
class DashboardView(APIView):
permission_classes = [AllowAny]
def get(self, request):
m1 = Model1.objects.all()
m1_serializer = Model1Serializer(sliders, many=True)
m2 = Model2.objects.all()
m2_serializer = Model2Serializer(
collections, many=True
)
m3 = Model3.objects.all()
m3_serializer = Model3Serializer(categories, many=True)
response = {
"m1": m1_serializer.data,
"m2": m2_serializer.data,
"m3": m3_serializer.data,
}
return Response(response)
If a new record is added to any of these models, cache_page should be deleted.
signals.py
#receiver(post_save, sender=Model1)
def clear_cache_when_update_model1(
sender, instance, created, **kwargs
):
if created:
pass
#receiver(post_save, sender=Model2)
def clear_cache_when_update_model2(
sender, instance, created, **kwargs
):
if created:
pass
#receiver(post_save, sender=Model3)
def clear_cache_when_update_model3(
sender, instance, created, **kwargs
):
if created:
pass
I couldn't find the key of cache_page and couldn't find how to clear cache_page.

How to remove fields from serializer

from .models import Catagory, CatagoryItems, MiniItems
from rest_framework import serializers
class MiniItemsSerializer(serializers.ModelSerializer):
class Meta:
Mini = serializers.StringRelatedField(read_only=False,many=True)
model = MiniItems
fields ="__all__"
read_only_fields = ("Mini","id")
depth=2
def validate_url(self, value):
if value and len(value) > 0:
raise serializers.ValidationError('Error')
return value
def create(self, validated_data):
if "catagory" in validated_data:
del validated_data["catagory"]
return Message.objects.create(**validated_data)
class CatagoryItemsSerializer(serializers.ModelSerializer):
class Meta:
items = serializers.ListField(child=MiniItemsSerializer())
model = CatagoryItems
fields ="__all__"
read_only_fields = ("id","items")
# def validate_url(self, value):
# if value and len(value) > 0:
# raise serializers.ValidationError('Error')
# return value
# def create(self, validated_data):
# if "catagory" in validated_data:
# del validated_data["catagory"]
# return Message.objects.create(**validated_data)
class CatagorySerializer(serializers.ModelSerializer):
class Meta:
ser = serializers.SerializerMethodField('removt')
catagoryItems =serializers.SlugRelatedField(
many=True,
read_only=True,
slug_field='name'
)
id = serializers.Field()
model = Catagory
fields =("id","name","desc","status","catagoryItems",)
read_only_fields = ("catagoryItems",)
depth=1
def removet(self, *args, **kwargs):
super(CatagorySerializer, self).__init__(*args, **kwargs)
if 'catagory' in kwargs:
if 'request' in kwargs['catagory']:
tabs = kwargs['catagory']['request'].query_params.getlist('catagory', [])
if tabs:
# tabs = tabs.split(',')
# included = set(tabs)
existing = set(self.fields.keys())
for other in existing - included:
self.fields.pop(other)
You should use exclude, here are the docs

Django Crispy Form With Dynamically Defined Field

I would like to define a Django crispy form with two statically defined fields (name and description) and one dynamically defined field (enum_value). I do it like this:
class DataTypeForm(forms.Form):
name = forms.CharField()
description = forms.CharField(widget=forms.Textarea)
def __init__(self, *args, **kwargs):
self.mode = kwargs.pop('mode')
super(DataTypeForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.wrapper_class = 'row'
self.helper.label_class = 'col-md-2'
self.helper.field_class = 'col-md-8'
self.helper.add_input(Submit('submit', 'Submit'))
self.fields['enum_value'] = forms.CharField()
The dynanic field enum_value is defined in the last line. Unfortunately, this does not work as expected: the form is rendered with the two static fields (name and description) but the dynamic field enum_value is not visible. The problem seems to lie in the use of the FormHelper class. If I modify my example as follows:
class DataTypeForm(forms.Form):
name = forms.CharField()
description = forms.CharField(widget=forms.Textarea)
def __init__(self, *args, **kwargs):
self.mode = kwargs.pop('mode')
super(DataTypeForm, self).__init__(*args, **kwargs)
# self.helper = FormHelper(self)
# self.helper.wrapper_class = 'row'
# self.helper.label_class = 'col-md-2'
# self.helper.field_class = 'col-md-8'
# self.helper.add_input(Submit('submit', 'Submit'))
self.fields['enum_value'] = forms.CharField()
then, things work as expected and all three fields in my form are correctly rendered. Is there any way I can get the dynamically defined field correctly rendered even when using the FormHelper class?
Placing the dynamic field enum_value before the creation of the FormHelper should work.
class DataTypeForm(forms.Form):
name = forms.CharField()
description = forms.CharField(widget=forms.Textarea)
def __init__(self, *args, **kwargs):
self.mode = kwargs.pop('mode')
super(DataTypeForm, self).__init__(*args, **kwargs)
self.fields['enum_value'] = forms.CharField()
self.helper = FormHelper(self)
self.helper.wrapper_class = 'row'
self.helper.label_class = 'col-md-2'
self.helper.field_class = 'col-md-8'
self.helper.add_input(Submit('submit', 'Submit'))

Updating object using super().update()

I have an object, whose attributes I would like to update. Right now, I am able to update a single attribute eg: name. But the object has several attributes which include name, contact_name, contact_email and contact_phone_number.The following is what I have at the moment.
In views.py
class MerchantViewSet(GetPrefixedIDMixin, viewsets.ModelViewSet):
"""POST support for /merchants/."""
print ("in MerchantViewSet")
queryset = models.Merchant.objects.all()
serializer_class = serializers.CreateMerchantSerializer
lookup_field = "id"
# lookup_value_regex = f"{models.Merchant.id_prefix}_[a-f0-9]{32}"
lookup_value_regex = ".*"
permission_classes = [permissions.MerchantPermission]
def get_queryset(self):
"""Filter the queryset based on the full merchant name or starting with a letter."""
queryset = models.Merchant.objects.all()
search_param = self.request.query_params.get("search", None)
if search_param:
if search_param.startswith("^"):
queryset = queryset.filter(name__istartswith=search_param[1:])
else:
queryset = queryset.filter(name__icontains=search_param)
return queryset
def update(self, request, *args, **kwargs):
request.data["user"] = request.user.id
print (self.get_object().name)
print (request.data)
merchant = self.get_object()
print (response)
print (merchant.name)
for merchants in models.Merchant.objects.all():
print (merchants.id)
if (merchants.id == merchant.id):
merchants.name = request.data["name"]
merchants.save()
I've tried using
response = super(MerchantViewSet, self).update(request, *args, **kwargs)
from what I read in the DRF documentations but using this just returns error 404 when I run my test. Do I simply have to do with I did with name in my code above, with the other attributes? Or is there a more streamlined way to do this?
This is my test:
class MerchantsViewSetTest(tests.BigETestCase): # noqa
#classmethod
def setUpClass(cls): # noqa
super(MerchantsViewSetTest, cls).setUpClass()
cls.application = tests.get_application()
tests.create_group("merchant")
cls.consumer_user = tests.create_consumer()
cls.admin = tests.create_administrator()
cls.merchant_geraldine = models.Merchant.objects.create(
name="Test Account 1",
contact_name="Geraldine Groves",
contact_email="geraldine#example.com",
contact_phone_number="+35310000000",
)
cls. merchant_barbara = models.Merchant.objects.create(
name="Account 2",
contact_name="Barbara",
contact_email="barbara#example.com",
contact_phone_number="+35310000432",
)
def test_edit_merchant(self): # noqa
url = reverse("bige_transactions:merchant-detail", kwargs={'id': self.merchant_geraldine.prefixed_id})
# payload
data = {"name": "Edited"}
# Put data
resp_data = self.put(url, data, user=self.admin, status_code=200)
# Check if data was updated
url = reverse("bige_transactions:merchant-list")
# Try without authenticated user
self.get(url, status_code=401)
# Try with authenticated admin user
resp_data = self.get(url, user=self.admin, status_code=200)
print (resp_data)

post method in generic class based view is not called upon form submission in Django?

I have a written a mixin that overrides the POST and get_from_kwargs of CreateView. I am doing AJAX submission of my form. I see that get_from_kwargs is called by printing on the console. But none of the other methods such as post,form_valid or form_invalid is being called. I have placed print statements in these methods but none of them is being called.
Here is my mixin:
class PendFormMixin(object):
form_hash_name = 'form_hash'
pend_button_name = 'pend'
def get_form_kwargs(self):
"""
Returns a dictionary of arguments to pass into the form instantiation.
If resuming a pended form, this will retrieve data from the database.
"""
pk_form = self.kwargs.get('pk', None)
form_hash = None
if pk_form:
form_data = PendedForm.objects.get(pk=pk_form)
form_hash = form_data.hash
print "form_hash", form_hash
if form_hash:
import_path = self.get_import_path(self.get_form_class())
print import_path
print self.get_pended_data(import_path, form_hash)
return {'data': self.get_pended_data(import_path, form_hash)}
else:
print "called in kwargs"
# print super(PendFormMixin, self).get_form_kwargs()
return super(PendFormMixin, self).get_form_kwargs()
def post(self, request, *args, **kwargs):
"""
Handles POST requests with form data. If the form was pended, it doesn't follow
the normal flow, but saves the values for later instead.
"""
self.object = None
if self.pend_button_name in self.request.POST:
print "here"
form_class = self.get_form_class()
print form_class
form = self.get_form(form_class)
# print "form is ", form
self.form_pended(form)
return super(PendFormMixin, self).post(request, *args, **kwargs)
else:
print "here in post"
return super(PendFormMixin, self).post(request, *args, **kwargs)
# Custom methods follow
def get_import_path(self, form_class):
return '{0}.{1}'.format(form_class.__module__, form_class.__name__)
def get_form_hash(self, form):
content = ','.join('{0}:{1}'.format(n, form.data[n]) for n in form.fields.keys())
return md5(content).hexdigest()
def form_pended(self, form):
import_path = self.get_import_path(self.get_form_class())
form_hash = self.get_form_hash(form)
pended_form = PendedForm.objects.get_or_create(form_class=import_path,
hash=form_hash)
for name in form.fields.keys():
pended_form[0].data.get_or_create(name=name, value=form.data[name])
return form_hash
def get_pended_data(self, import_path, form_hash):
print "import_path is", import_path
print "form_hash is", form_hash
# data = PendedValue.objects.filter(import_path=import_path, form_hash=form_hash)
data = PendedValue.objects.filter(form__form_class=import_path, form__hash=form_hash)
print "data ", data
return dict((d.name, d.value) for d in data)
Here is my view:
class ArticleCreateView(PendFormMixin, CreateView):
form_class = ArticleForm
model = Article
template_name = "article_create.html"
# success_url = reverse_lazy('blog_create')
success_url = '/admin'
def form_valid(self, form):
"""
If the request is ajax, save the form and return a json response.
Otherwise return super as expected.
"""
if self.request.is_ajax():
self.object = form.save()
time.sleep(5)
print "here"
return HttpResponse(json.dumps("success"),
mimetype="application/json")
if self.pend_button_name in self.request.POST:
print "in the form_valid"
return
print "in the form_valid"
return super(ArticleCreateView, self).form_valid(form)
def form_invalid(self, form):
"""
We haz errors in the form. If ajax, return them as json.
Otherwise, proceed as normal.
"""
print "self is ", self.request.POST
if self.request.is_ajax():
return HttpResponseBadRequest(json.dumps(form.errors),
mimetype="application/json")
if self.pend_button_name in self.request.POST:
print "in the form_invalid"
return redirect('/admin')
return super(ArticleCreateView, self).form_invalid(form)
unable to figureout what is going wrong. It used to work previously, before I used the mixin. but by introducing the above mixin, the code does not work any more.

Resources