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

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...)

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.

Wagtail | ChoiceField not displayed

I am facing some kind of incomprehensible behavior working with "StructBlock" and "ChoiceBlock". For some reasons the ChoiceBlock dont show up in admin-menu if designed via class-notation.
Like that no choiceblock is shown at admin-menu. Only the textblock.
class CodeBlock(blocks.StructBlock):
code = blocks.TextBlock(required=True)
type = blocks.ChoiceBlock(choices=[
('markup', 'markup'), ('css', 'css')
], required=True),
class Meta:
template = 'home/blocks/code.html'
class HomePageIndex(Page):
body = StreamField([('code', CodeBlock())])
content_panels = Page.content_panels + [
StreamFieldPanel('body'),
]
The following solution is in my opinion pretty much equal, but works well. I do not get why... Due the fact i need the Struct-Field more often i prefer the the class-notation.
class HomePageIndex(Page):
body = StreamField([
('code', blocks.StructBlock([
('code', blocks.TextBlock(required=True)),
('type', blocks.ChoiceBlock(choices=[
('markup', 'markup'), ('css', 'css')
], template='home/blocks/code.html'))
])
content_panels = Page.content_panels + [
StreamFieldPanel('body'),
Not get me wrong, i could make it work (using the bad-way). I wonder why that happens.
Ty in advance

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

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),
...
]

Wagtail admin ,CheckboxSelectMultiple not saving data

#register_snippet
class Numbers(models.Model):
number = models.IntegerField()
class State(models.Model):
state = models.CharField(max_length=100)
number = ParentalManyToManyField(Numbers)
class HomeStateNumber(State):
page = ParentalKey('home.HomePage', related_name='helpline')
api_fields = ['state', 'number']
panels = [
FieldPanel('state'),
FieldPanel('number',widget=forms.CheckboxSelectMultiple),
]
class HomePage(Page):
content_panels = [
FieldPanel('title'),
ImageChooserPanel('cover_page'),
InlinePanel('ticker', label="ticker"),
InlinePanel('helpline', label="helpline"),
]
I want to add one than more number in a state , wagtail shows correct order in admin , when you select number from multiple and save the page, data is not saved. It remains None (queryset)
Is there any other way to do this ?
I think i am doing wrong somewhere
Please help
Models using ParentalManyToManyField need to inherit from modelcluster.models.ClusterableModel.
from modelcluster.models import ClusterableModel
class State(ClusterableModel):
state = models.CharField(max_length=100)
number = ParentalManyToManyField(Numbers)
Also, make sure you have django-modelcluster version 4.0 (or above) installed - older versions had a bug preventing m2m relations in inline objects from working.

Dynamic form choice/option field in django

I have made this web app https://notes12345.herokuapp.com
The code of this app is here: https://github.com/theparadoxer02/Notes
What is want is to make the search form in such a way that when user select the year the choices for branches get generated dynamically , means only that branch options should come that of selected year, when when one is done selecting the branch , then the options for subjects should come that are related to selected year,branch above.
So in this way I want to generate form .
What should I learn? where should i modify file in views.py or models.py ? I am stuck with that .
Here is my model file:
year_choices = (
( 1 , 'First' ),
( 2 , 'Second'),
( 3 , 'Third' ),
( 4 , 'Fourth')
)
branch_choices = (
( 'IT','IT' ),
( 'EE','EE' ),
( 'CSE','CSE'),
( 'EC','EC' ),
( 'ME','ME' ),
( 'CE','CE' ),
)
subject_choices = (
( 'DS' , 'Data Structure' ),
( 'OS' , 'Operating sytem' ),
( 'EC' , 'Ecomomics' ),
( 'Thermo' , 'Thermo' ),
)
def generate_picture_name(instance, filename):
url = "images/{0}_{1}_{2}.jpg".format(
instance.subjects.branch, instance.subjects.year, instance.unit_no)
return url
class Subject(models.Model):
name = models.CharField(max_length=20)
no_of_units = models.IntegerField()
year=models.IntegerField(choices=year_choices)
branch=models.CharField(choices=branch_choices,max_length=15)
def __str__(self):
return self.name
class Note(models.Model):
#unit_choices = ((1,'1'),(2,'2'),(3,'3'),(4,'4'),(5,'5'),(6,'6'),(7,'7'),(8,'8'),(9,'9'),(10,'10'))
#branch = models.CharField(max_length=55,choices=branch_choices)
#year = models.IntegerField(choices = year_choices)
#subject_name = models.CharField(choices=subject_choices,max_length=10)
subjects = models.ForeignKey(Subject,on_delete=models.CASCADE)
unit_no = models.IntegerField()
picture = models.ImageField(upload_to = generate_picture_name)
def __str__(self):
return str(self.id)
You can easily do this using intercooler.js specifically by its dependent Select feature as mention in docs.
You have two options:
Either do everything server-side.
In that case split the search form in two django views.
First view, display a form with the year only. Submit the form to the second view (using <form action="url_for_second_view">
Second view, read the selected year, and respond with a form for the other options.
Or use javascript on the client to dynamically set the available options when the year changes.

Categories

Resources