Nested categories/InlinePanel(s) in wagtail - wagtail

I struggle to implement something like "nested categories":
PageA:
- Cat1
- SubCat1
- SubCat2
- ...
- Cat2
- SubCat1
- ...
All categories and subcategories should be orderable and editable by an editor.
My guess was something like this:
class CategoryTestPage(Page):
content_panels = Page.content_panels + [
InlinePanel('categories')
]
class Category(Orderable,ClusterableModel,models.Model):
page = ParentalKey(CategoryTestPage, related_name='category')
category = models.CharField(max_length=250)
def __str__(self):
return "%d %s" % (self.id, self.category)
panels = [
FieldPanel('category'),
InlinePanel('subcategory')
]
class SubCategory(Orderable,models.Model):
category = ParentalKey(ProjektOrdnung, related_name='subcategory')
subcategory = models.CharField(max_length=250)
def __str__(self):
return "%d %s" % (self.id, self.subcategory)
panels = [
FieldPanel('subcategory')
]
But this results in 'CategoryForm' object has no attribute 'formsets'. It seems nested InlinePanels are the problem?
Further I need this "hierarchical taxonomy" for assigning some of these categories/subcategories to other pages:
PageB:
- has Cat1
- has SubCa2
- ...
... which looks a lot like hierarchical tags...
Any ideas how to implement this or what's wrong with my implementation?
Kind regards,
tombreit
PS: I'm on wagtail 1.2rc1

Here's one way to do it, with much room for interface improvements ;) In order to sort the categories at the page level, I'd suggest the use of django-sortedm2m.
from wagtail.wagtailcore.models import Orderable, Page
from wagtail.wagtailsnippets.models import register_snippet
from django.db import models
#register_snippet
class Category(models.Model):
name = models.CharField(
max_length=80, unique=True, verbose_name=_('Category Name'))
slug = models.SlugField(unique=True, max_length=80)
parent = models.ForeignKey(
'self', blank=True, null=True, related_name="children",
help_text=_(
'Categories, unlike tags, can have a hierarchy. You might have a '
'Jazz category, and under that have children categories for Bebop'
' and Big Band. Totally optional.')
)
description = models.CharField(max_length=500, blank=True)
class Meta:
ordering = ['name']
verbose_name = _("Category")
verbose_name_plural = _("Categories")
panels = [
FieldPanel('name'),
FieldPanel('parent'),
FieldPanel('description'),
]
def __str__(self):
return self.name
def clean(self):
if self.parent:
parent = self.parent
if self.parent == self:
raise ValidationError('Parent category cannot be self.')
if parent.parent and parent.parent == self:
raise ValidationError('Cannot have circular Parents.')
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
return super(Category, self).save(*args, **kwargs)
class CategoryPage(models.Model):
category = ParentalKey('Category', related_name="+", verbose_name=_('Category'))
page = ParentalKey('MyPage', related_name='+')
panels = [
FieldPanel('category'),
]
class MyPage(Page):
categories = models.ManyToManyField(Category, through=CategoryPage, blank=True)
content_panels = Page.content_panels + [
FieldPanel('categories'),
]

Related

Django Post request for many to many field ValueError

I am working on a post request in which the user chooses from a list of tags and makes combinations of tags. The combination of tags should then be posted. Nothing should get changed in the Tag table.
These are the models:
models.py
class Tag(models.Model):
name = models.CharField(max_length=256)
language = models.CharField(max_length=256)
objects = models.Manager()
def __str__(self):
"""Return a human readable representation of the model instance."""
return self.name or ''
#property
def tags(self):
tags = self.tagging.values('tag')
return tags.values('tag_id', 'tag__name', 'tag__language')
class Combination(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, null=True)
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, null=True)
tag_id = models.ManyToManyField(Tag, null=True)
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
objects = models.Manager()
def __str__(self):
return str(self.tag_id) or ''
This is the serializer for Combination.
serializers.py
class CombinationSerializer(serializers.ModelSerializer):
tag_id = TagWithIdSerializer(many=True, required=False, write_only=False)
resource_id = serializers.PrimaryKeyRelatedField(queryset=Resource.objects.all(),
required=True,
source='resource',
write_only=False)
gameround_id = serializers.PrimaryKeyRelatedField(queryset=Gameround.objects.all(),
required=False,
source='gameround',
write_only=False)
user_id = serializers.PrimaryKeyRelatedField(queryset=CustomUser.objects.all(),
required=False,
source='user',
write_only=False)
class Meta:
model = Combination
depth = 1
fields = ('id', 'user_id', 'gameround_id', 'resource_id', 'tag_id', 'created', 'score')
def create(self, validated_data):
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
score = 0
tag_data = validated_data.pop('tag_id', None)
combination = Combination(
user=user,
gameround=validated_data.get("gameround"),
resource=validated_data.get("resource"),
created=datetime.now(),
score=score
)
combination.save()
for tag_object in tag_data[0]:
combination.tag_id.add(tag_object)
return combination
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['tag_id'] = TagWithIdSerializer(instance.tag_id.all(), many=True).data
return rep
I have tried posting the following JSON object to the database:
{
"gameround_id": 2015685170,
"resource_id": 327888,
"tag_id": [{"id": 2014077506, "name": "corwn","language": "en"}]
}
I am getting a ValueError: Field 'id' expected a number but got 'name'.
How can I fix this issue?
you need to provide tag id for each tag not all tag data,
Try like this
{
"gameround_id": 2015685170,
"resource_id": 327888,
"tag_id": [2014077506,2014077507]
}

__str__ returned non-string (type Category). when I add post from admin

After I added user and date_added in the Photo models, when I add post from admin its throws me an error saying: str returned non-string (type Category), when I click on the addpost link in the home template its throw another error: 'tuple' object has no attribute 'name'. how can I solve that ?
the models.py:
from django.db import models
from cloudinary.models import CloudinaryField
from django.contrib.auth.models import User
# Create your models here.
class Category(models.Model):
name = models.CharField(max_length=100, null=False, blank=False)
def __str__(self):
return self.name
class Photo(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True,
blank=True)
image = CloudinaryField('image')
description = models.TextField(null=True)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.category
the view.py file:
def home(request):
category = request.GET.get('category')
if category == None:
photos = Photo.objects.all()
else:
photos = Photo.objects.filter(category__name=category)
categories = Category.objects.all()
context = {'categories': categories, 'photos': photos}
return render(request, 'home.html', {'categories': categories, 'photos': photos} )
def viewPhoto(request, pk):
photo = Photo.objects.get(id=pk)
return render(request, 'photo.html', {'phpto': photo})
class PostCreativeView(LoginRequiredMixin, CreateView):
model = Photo, Category
fields = ['description', 'image', 'category', 'name']
template_name = 'post_create.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super (PostCreativeView, self).form_valid(form)
Well it has to do with your category name and model you added into post create view.py and so you
have do something like this:
views.py
class PostCreativeView(LoginRequiredMixin, CreateView):
model = Photo
fields = ['description', 'image', 'category']
template_name = 'post_create.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super (PostCreativeView, self).form_valid(form)
#.......
# Models.py
class Photo(models.Model):
#>>>...
def __str__(self):
return str(self.category)
You should return the str(…) of the category, so:
class Photo(models.Model):
# …
def __str__(self):
return str(self.category)

workaround for two dimensional Arrays in django

I would like to make a Ingredientslist for recipies(model named articles) and therefore need to assign unique values to Ingredients out of a Many to Many Infredient list.
I know that there is no straight forward way to implement a two dimensional array in django but I hope someone here has had this issue and knows a workaround.
Here I have my models being part of this issue:
class Ingredient(models.Model):
name = models.CharField(max_length=200, null=False)
kcal = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(800)], blank=True, default=0)
carbs = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(99)], blank=True, default=0)
protein = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(99)], blank=True, default=0)
fat = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(99)], blank=True, default=0)
class Article(models.Model):
banner = models.ImageField(null=True, default='dashboard-BG.jpg')
headline = models.CharField(max_length=200, null=False, default='Unnamed')
subtitle = models.CharField(max_length=300, null=False, default='Unnamed')
article_body = models.CharField(max_length=4000, null=False, default='Lorem Ipsum')
date_created = models.DateTimeField(auto_now_add=True)
ingredientList = models.BooleanField(null=True, default=False)
ingredients = models.ManyToManyField(Ingredient, blank=True)
kcal = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(3000)], null=True, blank=True)
carbs = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(3000)], null=True, blank=True)
protein = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(3000)], null=True, blank=True)
fat = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(3000)], null=True, blank=True)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
ingredients = self.ingredients
totalCalories = 0
totalCarbs = 0
totalProtein = 0
totalFat = 0
for i in ingredients:
totalCalories += i.kcal
totalCarbs += i.carbs
totalProtein += i.protein
totalFat += i.fat
if i == 0:
return
self.kcal = totalCalories
self.carbs = totalCarbs
self.protein = totalProtein
self.fat = totalFat
self.save()
def __str__(self):
return self.headline
The 'two dimensional array should be the ingredients model field. Many ingredients having a specific unique amount for this recipe.
Thx for all the answers in advance ;)
In Django, models are representations of fields in SQL datatables. You could look into the JSONField type. This Field will hold any form of JSON data, basically a python dictionary. You could have one key for the ingredient and one key for the amount. Hope this helps!

Using Sorted on a set of a QuerySet

I have this Page model:
class BlogPostPage(Page):
date_published= models.DateField('Published date', blank=True, null=True)
intro= models.CharField(max_length=250)
body = RichTextField(blank=True)
header_image = models.ForeignKey('wagtailimages.Image', blank=True, null=True,
on_delete=models.SET_NULL, related_name='+')
tags = ClusterTaggableManager(through='blog.BlogPageTag', blank=True)
categories = ParentalManyToManyField('blog.BlogCategory', blank=True)
def get_context(self, request):
context = super().get_context(request)
all_categories = []
for post in self.get_siblings(inclusive=True):
all_categories +=post.specific.categories.all()
all_categories = sorted(set(all_categories))
context['all_categories']= all_categories
return context
When the page is access I get a TypeError:
TypeError at /blog/xxx/
'<' not supported between instances of 'BlogCategory' and 'BlogCategory'
Inputs on why this is happening and how it can be avoided are requested.
You can define the key parameter for sorted, according to key functions.
But I am not sure, is + allowed in: all_categories +=post.specific.categories.all()?

Second page = ParentalKey() definition, to use one snippen in different Page models

I defined two Relationship(Orderable, models.Model) to be able to use one modelsnippet inside differen Page models like:
class GroupstageTournamentModel(models.Model):
...
class GroupstageTournamentRelationship(Orderable, models.Model):
page = ParentalKey('TournamentPage',
related_name='groupstage_tournament_relationship')
match = models.ForeignKey('GroupstageTournamentModel',
related_name='match_tournament_relationship')
panels = [
PageChooserPanel('match')
]
class MatchesScreencastRelationship(Orderable, models.Model):
page = ParentalKey('ScreencastPage',
related_name='groupstage_screencast_relationship')
match = models.ForeignKey('GroupstageTournamentModel',
default="", related_name='match_screen_relationship')
panels = [
PageChooserPanel('match')
]
class TournamentPage(Page):
starts_at = models.DateTimeField(blank=True)
ends_at = models.DateTimeField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('title'),
FieldPanel('starts_at'),
FieldPanel('ends_at'),
InlinePanel(
'groupstage_tournament_relationship', label="Group game:",
panels=None, min_num=1),
]
def __str__(self):
return self.title
class ScreencastPage(Page):
content_panels = Page.content_panels + [
FieldPanel('title'),
InlinePanel(
'groupstage_screencast_relationship', label="Playing First",
panels=None, max_num=1),
]
parent_page_types = ['home.HomePage']
subpage_types = []
def __str__(self):
# return self.title
return '{} \n Nächste: {}'.format(self.groupstage_relationship, self.final_phase_relationship)
As you can see my idea was to use one of them insite TournamentPage and another inside ScreencastPage. If i do that this way i get this error:
ERROR:
django.core.exceptions.FieldError: Local field 'id' in class 'GroupstageTournamentRelationship' clashes with field of the same name from base class 'GroupstageTournamentModel'.
how can this problem be solved? Is it possible to add somehow the second ParentalKey for relationship with ScreencastPage and use it direct inside GroupstageTournamentModel like you did in backerydemo?
UPDATE
I changed GroupstageTournamentModel from model.Model to ClusterableModel and i changed related_name so that it different in both of related_name's
related_name='groupstage_tournament_relationship' and related_name='groupstage_screencast_relationship'. I did migrations again and got the same error. Here is my GroupstageTournamentRelationship class:
class GroupstageTournamentModel(ClusterableModel):
number = models.PositiveSmallIntegerField(
help_text="Add the unique number of this Match.")
starts_at = models.DateTimeField()
# Team 1
team_1 = models.ForeignKey(
TeamRooster,
null=True, blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
team_1_dress = ColorField(default='#ff0000', blank=True)
team_1_first_halftime_score = models.PositiveSmallIntegerField(blank=True, default="")
# Team 2
team_2 = models.ForeignKey(
TeamRooster,
null=True, blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
team_2_dress = ColorField(default='#0066ff', blank=True)
team_2_first_halftime_score = models.PositiveSmallIntegerField(blank=True, default="")
panels = [
FieldPanel('number', classname="6"),
FieldPanel('starts_at', classname="6"),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('team_1', classname="6"),
FieldPanel('team_1_dress', classname="6"),
FieldPanel('team_1_first_halftime_score', classname="3"),
]),
], classname="full", heading="Team 1"),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('team_2', classname="6"),
FieldPanel('team_2_dress', classname="6"),
FieldPanel('team_2_first_halftime_score', classname="3"),
]),
], classname="full", heading="Team 2"),
]
def __str__(self):
return '{} vs {} {} - {}'.format(self.team_1, self.team_2, self.starts_at, self.number)
class Meta:
verbose_name = 'Gruppenphase Spiel'
verbose_name_plural = 'Gruppenphase'
UPDATE
In migration file i found following, and Im not sure what happens if I remove that:
UPDATE
I shortened the model and added "TournamentPage" for clarity.
I deleted my migration files included 0001_inicial.py and did again py manage.py makemigrations and it all worked !

Resources