Wagtail - Possible to rename an item in a streamfield in a migration? - python

I've changed the name of an item in a StreamField from
tiles = StreamField(
[
('items', StructBlock([
('icon', ImageChooserBlock()),
('text', CharBlock()),
])
),
], blank=True)
To
tiles = StreamField(
[
('info', StructBlock([
('icon', ImageChooserBlock()),
('text', CharBlock()),
])
),
], blank=True)
Any idea how I can create a migration to rename the field ?

Streamfield data is stored as JSON, as a list of items with 'type' and 'value' properties. To apply the field name change to all of your pages, you should be able to create an empty migration (or add it to an existing one), then add the following function or something similar to your migration file, then run it:
// other imports
import json
def convert_streamfield_name(apps, schema_editor):
db_alias = schema_editor.connection.alias
MyPageModel = apps.get_model('myapp', 'MyPageModel')
pages = MyPageModel.objects.using(db_alias).all()
for page in pages:
revised_stream_data = []
stream_data = page.tiles.stream_data
for data in stream_data:
if data.get('type') == 'items':
value = data.get('value')
revised_stream_data.append({
'type': 'info'
'value': value
})
else:
revised_stream_data.append(data)
raw_json = json.dumps(revised_stream_data)
page.tiles = raw_json
page.save()
class Migration(migrations.Migration):
dependencies = [...]
operations = [
migrations.RunPython(convert_streamfield_name),
...
]

Related

Value Error: Field 'id' expected a number but got 'create'

I was in the process of making a create gallery image field in Django for my project, but when I try to access the url, I'm getting a Field 'id' expected a number but got 'create'. error. And on postman, I'm getting a { detail: "Method \"POST\" not allowed." }.
class Gallery(models.Model):
SUBTLEPBR = "subtle"
AMULET = "amulet"
F8THFULPBR = "f8thfulpbr"
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
screenshot_by = models.CharField(max_length=200, null=False, blank=False)
image = WEBPField(
verbose_name=('Image'),
upload_to=image_folder,
default="placeholder.png"
)
PACKS = [
(SUBTLEPBR, 'SubtlePBR'),
(AMULET, 'Amulet'),
(F8THFULPBR, 'F8thfulPBR'),
]
pack = models.CharField(max_length=10, choices=PACKS)
def __str__(self):
return "Screenshot by "+ self.screenshot_by + " | " + self.pack
#api_view(["POST"])
#permission_classes([IsAdminUser])
def createGalleryImage(request):
user = request.user
gallery = Gallery.objects.create(
user = user,
screenshot_by = "John Doe",
pack = Gallery.SUBTLEPBR,
)
serializer = GallerySerializer(gallery, many=False)
return Response(serializer.data)
urlpatterns = [
path('admin/users/login/', views.MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('', views.getRoutes, name='routes'),
path("gallery/", views.GalleryImages, name="gallery"),
path("gallery/<str:pk>/", views.getGalleryImage, name="gallery-image"),
path("gallery/delete/<str:pk>/", views.deleteGalleryImage, name="gallery-delete"),
path("gallery/create/", views.createGalleryImage, name="gallery-create"),
path("updates/", views.PackUpdatesPage, name="updates"),
path("faq/", views.FaqPage, name="faq"),
path("subtle-roadmap/", views.SubtleRoadmapPage, name="subtle-roadmap"),
path("amulet-roadmap/", views.AmuletRoadmapPage, name="amulet-roadmap"),
path('admin/users/profile/', views.getUserProfile, name="user-profile")
]
The output that is supposed to happen is
{
"id": 51,
"screenshot_by": "Person",
"image": "/placeholder.png",
"pack": "subtle",
"user": 1
}
(added entire urls code to include entirety of paths)
This will "fire" the wrong view. Indeed, if you use /gallery/create, it will fire getGalleryImage with create as pk.
If the primary keys are integers, you can restrict these with:
urlpatterns = [
path('gallery/<int:pk>/', views.getGalleryImage, name='gallery-image'),
path(
'gallery/delete/<int:pk>/',
views.deleteGalleryImage,
name='gallery-delete',
),
path('gallery/create/', views.createGalleryImage, name='gallery-create'),
# …
]
This will then only "fire" if pk is a sequence of digits, like 1425, not create.

django-elasticsearch-dsl does not return all records that match the query condition

I'm writing a site on Django. I'm developing a site search system using django-elasticsearch-dsl. But I noticed a problem that not all records that meet the search condition are displayed.
For example, I know that I have 6 books in my database that contain the word 'Python', but when I use the command
books = BookDocument.search().query('match', title='Python')
django-elasticsearch-dsl gives me only 3 entries
books = BookDocument.search().query('match', title='Python')
for i in books:
print(i.title)
|||||||||||||
Укус Python
Програмуємо на Python
Django:Практика створення Web-сайтів на Python
And the remaining 3 are not displayed
Here is my BookDocument class:
#registry.register_document
class BookDocument(Document):
"""
Індексація таблиці БД Book для Elasticsearch-пошуку
Згідно із документацією django-elasticsearch-dsl
https://django-elasticsearch-dsl.readthedocs.io/en/latest/fields.html
"""
image = FileField()
category = ObjectField(properties={
'title': TextField(),
'pk': IntegerField()
})
authors = NestedField(properties={
'pk': IntegerField(),
'name': TextField()
})
class Index:
name = 'book'
settings = {
'number_of_shards': 1,
'number_of_replicas': 0
}
class Django:
model = Book
fields = [
'id',
'title',
'description',
]
related_models = [Category, Author]
def get_instances_from_related(self, related_instance):
if isinstance(related_instance, Category):
return related_instance.book_set.all()
if isinstance(related_instance, Author):
return related_instance.book_set.all()
Maybe someone has already encountered this and knows how to fix it

read_only but still populated in create method on serializer

I have a custom create method on my serializer for adding tags where the consumer could solely send a payload with the tags key containing a list of tags' names.
{
"id": 1,
"title": "Testing",
...
"tags": ["Python", "Django", "Go"]
}
Serializer:
class StreamSerializer(serializers.HyperlinkedModelSerializer):
streamer = StreamerSerializer()
tags = TagSerializer(many=True)
class Meta:
model = Stream
fields = [
"id",
"source",
"stream_id",
"started_at",
"collected_at",
"title",
"thumbnail_url",
"viewer_count",
"video_type",
"language",
"streamer",
"stream_data",
"tags",
"live_now",
]
extra_kwargs = {"tags": {"validators": []}}
def create(self, validated_data):
# pop streamer and tags from the payload
print(validated_data)
streamer_data = validated_data.pop("streamer")
tag_names = validated_data.pop("tags")
# get_or_create the streamer for this stream
streamer_user_id = streamer_data.pop("user_id")
streamer, created = Streamer.objects.get_or_create(
user_id=streamer_user_id, defaults=streamer_data
)
# use get_or_create on the stream to prevent duplicates if stream
# crashes or a node change and just update the existing stream
# with new data instead.
stream, created = Stream.objects.get_or_create(
streamer=streamer, defaults=validated_data
)
# add tags to the newly created stream
for tag_name in tag_names:
tag = Tag.objects.get(name=tag_name)
stream.tags.add(tag.id)
stream.save()
return stream
I would like for tags to have read_only=True, but by doing this I get a KeyError when posting to this endpoint since this is now excluded from any write methods.
class StreamSerializer(serializers.HyperlinkedModelSerializer):
streamer = StreamerSerializer()
tags = TagSerializer(many=True, read_only=True) # add read_only
...
What could I do in order to not have tags necessary to validate, but still have access to the field in my create method? Would I need a custom validator for this?
This doesn't exactly answer the question, but does achieve the goal I'm going for using to_internal_value on my TagSerializer.
class TagSerializer(serializers.HyperlinkedModelSerializer):
parent = ParentTagSerializer()
class Meta:
model = Tag
fields = ["name", "aliases", "parent"]
extra_kwargs = {"aliases": {"validators": []}}
# to_internal_value will iterate each tag name in the post payload
# and return the tag matching that name.
def to_internal_value(self, tag_name):
tag = Tag.objects.get(name=tag_name)
return tag
class StreamSerializer(serializers.HyperlinkedModelSerializer):
streamer = StreamerSerializer()
tags = TagSerializer(many=True)
...

How can I add a document on a StreamField in Wagtail CMS?

I've a question about Wagtail CMS.
Recently I'm trying to import programmatically some documents in a StreamField of an instance of a Wagtail Page model. I've done some researches but without results.
Currently I'm using:
Wagtail 1.13
Django 1.11.6
Python 2.7
Here the model of the page in which I need to import the documents as attachments (see the homonym field):
class EventPage(TranslatablePage, Page):
# Database fields
uuid = models.UUIDField(verbose_name='UUID', default=uuid.uuid4)
start_date = models.DateField(verbose_name='Start date')
end_date = models.DateField(verbose_name='End date')
location = models.CharField(verbose_name='Place', max_length=255, null=True, blank=True)
body = RichTextField(verbose_name='Body')
attachments = StreamField(blocks.StreamBlock([
('document', DocumentChooserBlock(label='Document', icon='doc-full-inverse')),
]), verbose_name='Attachments', null=True, blank=True)
subscribe = models.BooleanField(verbose_name='Subscribe option', default=False)
# Editor panels configuration
content_panels = [
FieldPanel('title', classname='title'),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('start_date'),
FieldPanel('end_date'),
]),
], heading='Period'),
FieldPanel('location'),
FieldPanel('body'),
StreamFieldPanel('attachments'),
]
promote_panels = Page.promote_panels + [
MultiFieldPanel([
FieldPanel('subscribe'),
], heading='Custom Settings'),
]
settings_panels = TranslatablePage.settings_panels + [
MultiFieldPanel([
FieldPanel('uuid'),
], heading='Meta')
]
parent_page_types = ["home.FolderPage"]
subpage_types = []
On shell, I tried to apply the solution explained on this page but without success.
event = EventPage.objects.get(pk=20)
doc = Document.objects.get(pk=3)
event.attachments = [
('document',
[
StreamValue.StreamChild(
id = None,
block = DocumentChooserBlock(),
value = doc
)
]
)
]
Python give me this error: AttributeError: 'list' object has no attribute 'pk'.
event.attachments = [('document', doc)] should work, I believe. (On the other question you link to, StreamChild was necessary because AccordionRepeaterBlock was a StreamBlock nested in a StreamBlock; that's not the case for your definition.)
To add a document to the existing StreamField content, build a new list and assign that to event.attachments:
new_attachments = [(block.block_type, block.value) for block in blocks]
new_attachments.append(('document', doc))
event.attachments = new_attachments
(Currently you can't append directly to a StreamField value, but this may well be supported in a future Wagtail release...)

Django REST serialize output - group by foreign keys

I have models like below.
Restaurant Model
class Restaurant(models.Model):
name = models.CharField(max_length=40, verbose_name='Name')
Menu Model
class Menu(models.Model):
name = models.CharField(max_length=40, unique=True, verbose_name='menu name')
Item Model
class Item(models.Model):
restaurant = models.ForeignKey(Restaurant)
menu = models.ForeignKey(Menu)
name = models.CharField(max_length=500)
price = models.IntegerField(default=0)
I want to get the menus for the shop id.
How can I group my results by menu for the restaurant id ?
call GET /menus/restaurant_id
Sample.
{
name: menu name 1
items: [ {item1}, {item2}]
},
{
name: menu name 2
items: [ {item1}, {item2}]
}
Thanks..
The only thing i can find it's postgres specific aggregation function ArrayAgg
You can use it like this:
from django.contrib.postgres.aggregates import ArrayAgg
Item.objects.filter(restaurant_id=1).values('menu__name').annotate(items=ArrayAgg('name'))
# example output:
# [
# {
# 'menu__name': 'menu1',
# 'items': ['item1', 'item2']
# },
# {
# 'menu__name': 'menu2',
# 'items': ['item3', 'item4']
# },
# ]
Such qs performs next raw sql query:
SELECT
"appname_menu"."name",
ARRAY_AGG("appname_item"."name") AS "items"
FROM "appname_item"
INNER JOIN "appname_menu" ON ("appname_item"."menu_id" = "appname_menu"."id")
WHERE "appname_item"."restaurant_id" = 1
GROUP BY "appname_menu"."name"
Probably it can help you.

Categories

Resources