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'))
Related
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)
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
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)
I'm trying to get a proof of concept of Django Rest Framework on getstream working to power inapp notifications.
My code https://github.com/morenoh149/django-rest-framework-getstream
models.py
class Snippet(models.Model, Activity):
created_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(
choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(
choices=STYLE_CHOICES, default='friendly', max_length=100)
owner = models.ForeignKey(
'auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
#property
def activity_actor_attr(self):
return self.owner
#property
def activity_notify(self):
return [feed_manager.get_notification_feed(self.owner_id)]
class Meta:
ordering = ('created_at', )
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(
style=self.style, linenos=linenos, full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
serializers.py
from django.contrib.auth.models import User from rest_framework import serializers
from snippets.models import Snippet
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(
view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner', 'title', 'code',
'linenos', 'language', 'style')
class ActivitySerializer(serializers.Serializer):
id = serializers.UUIDField()
foreign_id = serializers.CharField()
verb = serializers.CharField()
time = serializers.DateTimeField()
def __init__(self, *args, **kwargs):
object_serializer = kwargs.pop("object_serializer", None)
actor_serializer = kwargs.pop("actor_serializer", None)
super().__init__(self, *args, **kwargs)
if object_serializer:
self.fields["object"] = object_serializer()
else:
self.fields["object"] = serializers.CharField()
if actor_serializer:
self.fields["actor"] = actor_serializer()
else:
self.fields["actor"] = serializers.CharField()
class AggregatedSerializer(ActivitySerializer):
group = serializers.CharField()
activities = ActivitySerializer(many=True)
class NotificationSerializer(AggregatedSerializer):
is_seen = serializers.BooleanField()
is_read = serializers.BooleanField()
def get_activity_serializer(data, object_serializer=None, actor_serializer=None, **kwargs):
kwargs["object_serializer"] = object_serializer
kwargs["actor_serializer"] = actor_serializer
serializer = ActivitySerializer
if "is_seen" in data:
serializer = NotificationSerializer
elif "activities" in data:
serializer = AggregatedSerializer
return serializer(data, **kwargs)
views.py
class NotificationViewSet(viewsets.ViewSet):
"""
This viewset returns a notifications feed for the logged in user.
The feed contains events for when a relevant snippet is created.
"""
serializer_class = NotificationSerializer
def list(self, request):
feeds = feed_manager.get_news_feeds(self.request.user.id)
activities = feeds.get('timeline_aggregated').get()['results']
enriched_activities = enricher.enrich_aggregated_activities(activities)
serializer = get_activity_serializer(enriched_activities, SnippetSerializer, None, many=True)
return Response(serializer.data)
How do I get the /notifications/ endpoint to return the notifications for the signed in user?
Django get_initial is not populating product field in the form. I am expecting a drop down, with the queryset results as defined in the get_initial overridden function.
class PurchaseRequestDetailForm(forms.ModelForm):
class Meta:
model = PurchaseRequestDetail
fields = ["product", "variations", "quantity", "fulfilled", "vat", "discount", "surcharges", "active"]
exclude = ("purchase_request", )
class PurchaseRequestDetailCreateView(CreateView):
model = PurchaseRequestDetail
form_class = PurchaseRequestDetailForm
template_name = "inventory/purchaserequestdetail_form.html"
def get_pr_obj(self):
pr_id = self.request.session["pr_id"]
return PurchaseRequest.objects.get(id=pr_id)
def get_initial(self):
initial = super(PurchaseRequestDetailCreateView, self).get_initial()
try:
pr_obj = self.get_pr_obj()
initial["product"] = pr_obj.vendor.vendors_products.all()
except KeyError:
pass
self.form_class(initial)
return initial
template:
<td>{{ form.product|css_class:"form-control" }}</td>
An easy way to set a ModelChoiceField queryset is to set the field attribute in the form init();
class PurchaseRequestDetailForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# Get initial data passed from the view
self.product = None
if 'product' in kwargs['initial']:
self.product = kwargs['initial'].pop('product')
super(PurchaseRequestDetailForm, self).__init__(*args, **kwargs)
self.fields['product'].queryset = self.product
class Meta:
model = PurchaseRequestDetail
fields = ["product", "variations", "quantity", "fulfilled", "vat", "discount", "surcharges", "active"]
exclude = ("purchase_request", )
You should hook in to get_form_kwargs from ModelFormMixin to pass your data to the form.
class PurchaseRequestDetailCreateView(CreateView):
model = PurchaseRequestDetail
form_class = PurchaseRequestDetailForm
template_name = "inventory/purchaserequestdetail_form.html"
def get_pr_obj(self):
pr_id = self.request.session["pr_id"]
return PurchaseRequest.objects.get(id=pr_id)
def get_form_kwargs(self):
"""
Returns the keyword arguments for instantiating the form.
"""
kwargs = super(PurchaseRequestDetailCreateView, self).get_form_kwargs()
kwargs.update(
{'initial':
{'product': pr_obj.vendor.vendors_products.all()}
}
)
return kwargs