Wagtail: how to set calculated fields (#property) title in admin - wagtail

I use ModelAdmin module for my models in Wagtail.
I have #property fields in models, where I return some annotated data and display it Index and Inspect Views in Admin. But Wagtail set title of such fields as field name in model. In regular field I use verbose_name to set nice title, how can I change titles for property field?

You have to create your own ReadOnlyPanel as it is not possible with Wagtail.
mypanel.py
from django.forms.utils import pretty_name
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from wagtail.admin.edit_handlers import EditHandler
class ReadOnlyPanel(EditHandler):
def __init__(self, attr, *args, **kwargs):
self.attr = attr
super().__init__(*args, **kwargs)
def clone(self):
return self.__class__(
attr=self.attr,
heading=self.heading,
classname=self.classname,
help_text=self.help_text,
)
def render(self):
value = getattr(self.instance, self.attr)
if callable(value):
value = value()
return format_html('<div style="padding-top: 1.2em;">{}</div>', value)
def render_as_object(self):
return format_html(
'<fieldset><legend>{}</legend>'
'<ul class="fields"><li><div class="field">{}</div></li></ul>'
'</fieldset>',
self.heading, self.render())
def render_as_field(self):
return format_html(
'<div class="field">'
'<label>{}{}</label>'
'<div class="field-content">{}</div>'
'</div>',
self.heading, _(':'), self.render())
And to use it just import and use in your model:
from .mypanel import ReadOnlyPanel
class YourPage(Page):
content_panels = Page.content_panels + [
ReadOnlyPanel('myproperty', heading='Parent')
]
Original source: https://github.com/wagtail/wagtail/issues/2893

Related

How can I automaticall add the currently logged in user to django models in react [duplicate]

I have the following code working perfectly. I can create a Post object from DRF panel by selecting an image and a user. However I want DRF to populate the user field by the currently logged in user.
models.py
class Post(TimeStamped):
user = models.ForeignKey(User)
photo = models.ImageField(upload_to='upload/')
hidden = models.BooleanField(default=False)
upvotes = models.PositiveIntegerField(default=0)
downvotes = models.PositiveIntegerField(default=0)
comments = models.PositiveIntegerField(default=0)
serializers.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'user', 'photo']
views.py
class PhotoListAPIView(generics.ListCreateAPIView):
queryset = Post.objects.filter(hidden=False)
serializer_class = PostSerializer
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
How can I do this?
Off the top of my head, you can just override the perform_create() method:
class PhotoListAPIView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Give that a shot and let me know if it works
You can use CurrentUserDefault:
user = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
It depends on your use case. If you want it to be "write-only", meaning DRF automatically populates the field on write and doesn't return the User on read, the most straight-forward implementation according to the docs would be with a HiddenField:
class PhotoListAPIView(generics.ListCreateAPIView):
user = serializers.HiddenField(
default=serializers.CurrentUserDefault(),
)
If you want want it to be readable, you could use a PrimaryKeyRelatedField while being careful that your serializer pre-populates the field on write - otherwise a user could set the user field pointing to some other random User.
class PhotoListAPIView(generics.ListCreateAPIView):
user = serializers.PrimaryKeyRelatedField(
# set it to read_only as we're handling the writing part ourselves
read_only=True,
)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Finally, note that if you're using the more verbose APIView instead of generics.ListCreateAPIView, you have to overwrite create instead of perform_create like so:
class PhotoListAPIView(generics.ListCreateAPIView):
user = serializers.PrimaryKeyRelatedField(
read_only=True,
)
def create(self, validated_data):
# add the current User to the validated_data dict and call
# the super method which basically only creates a model
# instance with that data
validated_data['user'] = self.request.user
return super(PhotoListAPIView, self).create(validated_data)
You can avoid passing the user in your request and you won't see it in the output but DRF will populate it automatically:
from rest_framework import serializers
class MyModelSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = models.MyModel
fields = (
'user',
'other',
'fields',
)
As of DRF version 3.8.0 (Pull Request discussion), you can override save() in serializer.
from rest_framework import serializers
...
class PostSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Post
fields = ['id', 'user', 'photo']
def save(self, **kwargs):
"""Include default for read_only `user` field"""
kwargs["user"] = self.fields["user"].get_default()
return super().save(**kwargs)
#DaveBensonPhillips's answer might work in your particular case for some time, but it is not very generic since it breaks OOP inheritance chain.
ListCreateAPIView inherits from CreateModelMixin which saves the serializer already. You should always strive to get the full chain of overridden methods executed unless you have a very good reason not to. This way your code stays DRY and robust against changes:
class PhotoListAPIView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.validated_data['user'] = self.request.user
return super(PhotoListAPIView, self).perform_create(serializer)
You will have to override the default behavior of how generics.ListCreateAPIView creates an object.
class PhotoListAPIView(generics.ListCreateAPIView):
queryset = Post.objects.filter(hidden=False)
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
def get_serializer_class(self):
if self.request.method == 'POST':
return CreatePostSerializer
else:
return ListPostSerializer
def create(self, request, *args, **kwargs):
# Copy parsed content from HTTP request
data = request.data.copy()
# Add id of currently logged user
data['user'] = request.user.id
# Default behavior but pass our modified data instead
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
The .get_serializer_class() is not necessary as you can specify which fields are read-only from your serializer, but based on the projects I have worked on, I usually end up with 'asymmetric' serializers, i.e. different serializers depending on the intended operation.
Try this:
def post(self, request, format=None)
serializer = ProjectSerializer(data=request.data)
request.data['user'] = request.user.id
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST
This is what works for me in serializers.py, where I am also using nested data. I want to display created_by_username without having to lookup other users.
class ListSerializer(serializers.ModelSerializer):
"""
A list may be created with items
"""
items = ItemSerializer(many=True)
# automatically set created_by_id as the current user's id
created_by_id = serializers.PrimaryKeyRelatedField(
read_only=True,
)
created_by_username = serializers.PrimaryKeyRelatedField(
read_only=True
)
class Meta:
model = List
fields = ('id', 'name', 'description', 'is_public',
'slug', 'created_by_id', 'created_by_username', 'created_at',
'modified_by', 'modified_at', 'items')
def create(self, validated_data):
items_data = validated_data.pop('items', None)
validated_data['created_by_id'] = self.context['request'].user
validated_data['created_by_username'] = self.context['request'].user.username
newlist = List.objects.create(**validated_data)
for item_data in items_data:
Item.objects.create(list=newlist, **item_data)
return newlist
I wrote an extension to DRF's serializer below
from rest_framework import serializers
class AuditorBaseSerializer(serializers.Serializer):
created_by = serializers.StringRelatedField(default=serializers.CurrentUserDefault(), read_only=True)
updated_by = serializers.StringRelatedField(default=serializers.CurrentUserDefault(), read_only=True)
def save(self, **kwargs):
# if creating record.
if self.instance is None:
kwargs["created_by"] = self.fields["created_by"].get_default()
kwargs["updated_by"] = self.fields["updated_by"].get_default()
return super().save(**kwargs)
and it can be used as follows
class YourSerializer(serializers.ModelSerializer, AuditorBaseSerializer):
class Meta:
model = SelfEmployedBusiness
fields = (
'created_by',
'updated_by',
)

Render a form with initial values in WagTail

I need to render a form, with pre-filled data but it seems to not be possible in WagTail. I'm trying to do it the "Django way" using MyForm(initial={"field_name": value}) (MyForm inherits WagtailAdminModelForm if this is necessary), but this is not possible since MyForm doesn't have a Meta.model field and, as I understand, it cannot be specified due to how WagTail is designed.
Are there any work-arounds or alternative ways to render a pre-filled form?
# models.py
from django.db import models
from wagtail.core.models import Page
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.admin.forms import WagtailAdminPageForm
class BlogForm(WagtailAdminPageForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
my_field = self.fields["my_field"]
my_field.widget.attrs["value"] = "My new initial data"
class Blog(Page):
my_field = models.CharField(max_length=255, default='', blank=True)
content_panels = Page.content_panels + [
FieldPanel("my_field"),
]
base_form_class = BlogForm

Serializer will not return all values in the model

When I create a post request with json ex.
{
"title":"test",
"company" : "test",
"location" :"test",
"link" :"http://www.google.com/1"
}
The response I recieve is:
{"id":538,"link":"http://www.google.com/1"}
Why are not all of my fields saving to the database?
I've changed fields = '__all__' to fields = ('title', 'company', 'location', 'link') but I get an error:
TypeError at /api/listings/ Object of type TextField is not JSON
serializable
from django.db import models
# Model:
class Listing(models.Model):
title = models.TextField(max_length=100,blank=True),
company = models.TextField(max_length=50, blank=True),
location = models.TextField(max_length=50, blank=True),
link = models.URLField(max_length=250, unique=True)
#------------------------------------------------
from rest_framework import serializers
from listings.models import Listing
#Listing Serializer:
class ListingSerializer(serializers.ModelSerializer):
class Meta:
model = Listing
fields = '__all__'
#------------------------------------------------
from listings.models import Listing
from rest_framework import viewsets, permissions
from .serializers import ListingSerializer
#Listing Viewset:
class ListingViewSet(viewsets.ModelViewSet):
queryset = Listing.objects.all()
#.objects.all().delete()
permissions_classes = [
permissions.AllowAny
]
serializer_class = ListingSerializer
Ended up solving my issue by creating a new project with the same app setup. I am assuming there was some issue with the initial migration of the app model.

Adding tags with Django-Taggit

I have tried different tutorials for Django-taggit, but for some reason they all show how to add tags through Admin. I was wondering can I add tags using View and template while creating an instance of Model? or should I add tags to existing items only? Is there any recent tutorials for Django-Taggit or my be better app for Tags?
Their documentation is pretty swell. Once you have your model set up, you can use the tag field just like any other field in a form. It will automatically be set up to parse the tags.
Here is a very basic working example.
views.py
from django.shortcuts import render
from .models import NewspaperIndex
from .forms import NewIndexForm
def overview(request):
if request.method == "POST":
form = NewIndexForm(request.POST)
if form.is_valid():
form.save()
else:
form = NewIndexForm()
indexes = NewspaperIndex.objects.all()
context = {'form': form,
'indexes': indexes
}
return render(request, 'newsindex/overview.html', context)
models.py
from django.db import models
from taggit.managers import TaggableManager
class NewspaperIndex(models.Model):
title = models.CharField(max_length=200)
date = models.DateField()
abstract = models.TextField()
tags = TaggableManager()
def __str__(self):
return self.title
forms.py
import datetime
from django import forms
from django.forms import ModelForm
from .models import NewspaperIndex
class NewIndexForm(forms.ModelForm):
class Meta:
model = NewspaperIndex
fields = ['title', 'date', 'abstract', 'tags']
templates/newsindex/overview.html
<form class="" action="./" method="post">
{% csrf_token %}
{{form.as_p}}
<input type="submit" name="submit" value="Submit">
</form>
If you would like to add a tag from a shell, try:
tag='tag to add'
post=NewspaperIndex.objects.all()[0] #pick an object, to add tag to
post.tags.add(tag)

How to define external database url in api.py of django-tastypie?

I don't know how to define a url to my external database (a couch database) in the Resource. I have this:
class UserResource(Resource):
username=fields.CharField(attribute='username')
firstName=fields.CharField(attribute='firstName')
lastName=fields.CharField(attribute='lastName')
gender=fields.CharField(attribute='gender')
status=fields.IntegerField(attribute='status')
date=fields.DateTimeField(attribute='date')
class Meta:
object_class = ??
resource_name = 'users/list'
always_return_data = True
authorization= Authorization()
def get_object_list(self, request):
...
return results
def obj_get_list(self, request=None, **kwargs):
# Filtering disabled for brevity...
return self.get_object_list(request)
class UserResource(ModeslResource)
class Meta:
queryset = UserModel.objects.all().using('dbname')

Resources