Django - Formset created dynamically not saving - python

I've created a CreateView form with a Parent (named 'Entrada') model and (multiple) Children models (named 'BalaMateriesPrimeres') as formset. Those formset are created dynamically with a JS I've created so I can add as many children I want (instead of adding one by one with elo80ka dynamic formset plugin). But the problem is that the formset is invalid. When print the formset at the 'def post' method, I get the html tags but the 'value' field of each one is empty. This is the relevant code:
forms.py
class EntradaForm(ModelForm):
class Meta:
model = Entrada
exclude = ()
class BalaMateriesPrimeresForm(ModelForm):
class Meta:
model = BalaMateriesPrimeres
fields = ['quilos', 'material', 'cost_unitari']
BalaMateriesPrimeresFormSet = inlineformset_factory(Entrada, BalaMateriesPrimeres, form=BalaMateriesPrimeresForm, can_delete=True, extra=1)
views.py
class EntradaCreateUpdateView(LoginRequiredMixin, UpdateView):
model = Entrada
form_class = EntradaForm
formset_class = BalaMateriesPrimeresFormSet
def get_context_data(self, **kwargs):
data = super(EntradaCreateUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
data['bales_materies_primeres'] = BalaMateriesPrimeresFormSet(self.request.POST)
data['materials'] = Material.objects.all()
else:
data['bales_materies_primeres'] = BalaMateriesPrimeresFormSet()
data['materials'] = Material.objects.all()
return data
def get_object(self):
self.creating = 'pk' not in self.kwargs
if self.creating:
return None # Sucess
else:
obj = super().get_object()
return obj
def post(self, request, *args, **kwargs):
form = self.get_form()
formset = BalaMateriesPrimeresFormSet(request.POST, prefix='bales_materies_primeres')
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
def form_valid(self, form, formset):
self.object = form.save()
for fmset in formset:
fmset.save()
return super(EntradaCreateView, self).form_valid(form)
form.html
<form class="own-form" action="" method="post">
{% csrf_token %}
{% for hidden_field in form.hidden_fields %}
{{ hidden_field }}
{% endfor %}
<h2 class="text-center text-header"> Crear una nova entrada</h2>
<!-- Formulari d'entrada -->
<div class="form-group">
{% for field in form.visible_fields %}
<div class="form-group row">
<div class="col-4 text-center">{{ field.label_tag }}</div>
<div class="col-8">{{ field }}</div>
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
{% endfor %}
</div>
<!-- /Formulari d'entrada -->
<hr>
<!-- Formulari de Material -->
<div class="form-group form-material-box row form-0">
<div class="col-3 text-center">
<label>Pes aproximat: </label>
<input type="number" id="kg_0">
</div>
<div class="col-3 text-center">
<label>Bales a crear: </label>
<input type="number" id="num_boxes_0">
</div>
<div class="col-3 text-center">
<label>Material: </label>
<br>
<select name="item_id" id="material_0">
{% for material in materials %}
<option value="{{ forloop.counter }}">{{ material }}</option>
{% endfor %}
</select>
</div>
<div class="col-3 text-center">
<button type="button" id="create_boxes_0" class="btn btn-danger">Crea</button>
</div>
<!-- Nested forms with desired number of boxes -->
<div id="nested_forms_0">
{{ bales_materies_primeres.management_form }}
{% for bala in bales_materies_primeres %}
<div class="row" id="box_0">
<div class="col-3 text-center">
<h5>Bala #1: </h4>
</div>
<div class="col-2 text-center">
{{ bala.quilos }}
</div>
<div class="col-2 text-center" >
{{ bala.material }}
</div>
<div class="col-2 text-center" >
{{ bala.cost_unitari }}
</div>
<div class="col-3 text-center">
<button type="button" id='remove_box_0' class="btn btn-danger">Elimina Bala</button>
</div>
{% endfor %}
</div> <!-- /Bala form -->
</div> <!-- /Nested forms -->
</div> <!-- /Form -->
<!-- /Formulari de Material -->
<p>
<input id="crear" class="btn btn-secondary btn-lg btn-block btn-option btn-form" type="submit" value="Crear" />
</p>
javascript code:
https://pastebin.com/i87mqAaG
What I've tried:
-Yes, I've checked the TOTAL_FORMS value in the dev tools of Chrome. Also all the info is there as Django debug toolbar says
-When I print the formset right after getting it, it returns the HTML structure without values
-If I print the kwargs at the def post method I don't get anything.
I don't know what I'm missing but I had this problem for days.

Related

Django UpdateView Bootstrap Modal - i've got empty form in modal window

I've got empty Bootstrap modal form with Django UpdateView(CBV)
The most important question without using js , I will not be able to display data in a modal window? (I only use bootstrap.bundle.min.js in base.html)
Modal window show fields of empty form
view.py
class HistoryPamentsByService(ListView):
model=Payments
form_class=PaymentsForm
template_name ='myflat/history_by_service.html'
context_object_name='flats'
slug_url_kwarg = 'slug'
def get_context_data(self, **kwargs):
context=super().get_context_data(**kwargs)
form=PaymentsForm()
payments=Payments.objects.filter(flats_id=self.kwargs['flats_id'])
context['form']=form
return context
def get_form(self,*args,**kwargs):
super().get_form(*args, **kwargs)
form=PaymentsForm()
return form
def get_queryset(self):
return Payments.objects.filter(slug=self.kwargs['slug'],flats_id=self.kwargs['flats_id'])
class UpdatePayments(UpdateView):
model=Payments
pk_url_kwarg='pk'
template_name='myflat/update_modal.html'
context_object_name='name_flat'
fields=['name_service','amount_of_bill',
'amount_of_real',
'date_of_bill',
'date_of_payment',
'status_payment',
'comments',
'bill_save_pdf']
def get_success_url(self):
return reverse('HistoryPamentsByService',
kwargs={'slug':self.object.slug,
'flats_id': self.object.flats_id})
urls.py
urlpatterns = [ path('history_by_service/<slug:slug>/<int:flats_id>/',
HistoryPamentsByService.as_view(),name='HistoryPamentsByService'),
path('UpdatePayments/<int:pk>/',UpdatePayments.as_view(),name='UpdatePayments'),
]
template
history_by_service.html (for ListView)
{%extends "base.html"%}
{%block content%}
{% for flat in flats %}
<tr>
<td scope="row">{{ forloop.counter }} </td>
<td>{{flat.name_service}}</td>
<td>{{flat.amount_of_bill}}</td>
<td>{{flat.amount_of_real}}</td>
<td>{{flat.date_of_bill}}</td>
<td>{{flat.date_of_payment}}</td>
<td>{{flat.get_status_payment_display}}</td>
<td>{{flat.comments}}</td>
<td>{{flat.bill_save_pdf}}</td>
<td>
<form method="post" action="{% url 'UpdatePayments' flat.pk %}" enctype="multipart/form-data">
{% csrf_token %}
<button type='button' class='btn btn-success btn-sm' id="update_modal"
data-bs-toggle="modal" data-bs-target="#update_modal{{flat.pk}}">
<i class="fa-regular fa-pen-to-square fa-fw"></i>Edit</button>
{% include "myflat/update_modal.html" %}
</form>
{% endfor %}
{% endblock content %}
template
update_modal.html
<div class="modal fade" id="update_modal{{flat.pk}}" data-bs-backdrop="static"
data-bs-keyboard="false" tabindex="-1" aria-labelledby="update_modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content" style="width:auto">
<div class="modal-header">
<h5 class="modal-title " id="update_modalLabel">{{flat.name_flat}}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="post" action="{% url 'UpdatePayments' flat.pk %}" enctype="multipart/form-data">
<div class="modal-body">
{% csrf_token %}
{% for field in form %}
{{field}}
{% endfor %}
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" >
<i class="fa-solid fa-trash fa-fw" ></i>Save</button>
<button type="button" class="btn btn-warning"
data-bs-dismiss="modal">
<i class="fa-regular fa-circle-xmark"></i>Close</button>
</div>
</div>
</form>
</div>
</div>
</div>
I try add in UpdatePayments(UpdateView) next code:
class UpdatePayments(UpdateView):
model=Payments
pk_url_kwarg='pk'
form_class=PaymentsForm
template_name='myflat/update_modal.html'
context_object_name='name_flat'
def get_context_data(self, **kwargs):
context= super().get_context_data(**kwargs)
obj=Payments.objects.get(pk=self.kwargs['pk'])
form=PaymentsForm(self.request.POST or None ,instance=obj)
context['form']=form
return context
but still nothing in Modal is empty fields!!!
What's wrong, any helps,please!!!

improperly configured at /18/delete, Django views issue

I have searched through the other questions similar to my own problem and have come to no solution so im hoping someone can help me figure out where i went wrong.
I'm trying to implement a delete post option in my blog program but it is throwing the following error once you click the 'delete' button:
ImproperlyConfigured at /18/delete/
Deletepost is missing a QuerySet. Define Deletepost.model, Deletepost.queryset, or override Deletepost.get_queryset().
I am nearly sure its a problem with my URLS.py though what exactly i cannot figure out.
the following is the code in question:
Views.py
# delete post
class Deletepost(LoginRequiredMixin, DeleteView):
form_class = Post
success_url = reverse_lazy('blog:home')
template_name = 'templates/post.html'
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
urls.py
urlpatterns = [
# home
path('', views.postslist.as_view(), name='home'),
# add post
path('blog_post/', views.PostCreateView.as_view(), name='blog_post'),
# posts/comments
path('<slug:slug>/', views.postdetail.as_view(), name='post_detail'),
# edit post
path('<slug:slug>/edit/', views.Editpost.as_view(), name='edit_post'),
# delete post
path('<int:pk>/delete/', views.Deletepost.as_view(), name='delete_post'),
# likes
path('like/<slug:slug>', views.PostLike.as_view(), name='post_like'),
]
post.html
{% extends 'base.html' %} {% block content %}
{% load crispy_forms_tags %}
<div class="masthead">
<div class="container">
<div class="row g-0">
<div class="col-md-6 masthead-text">
<!-- Post title goes in these h1 tags -->
<h1 class="post-title text-success">{{ post.title }}</h1>
<!-- Post author goes before the | the post's created date goes after -->
<p class="post-subtitle text-success">{{ post.author }} | {{ post.created_on }}</p>
</div>
<div class="d-none d-md-block col-md-6 masthead-image">
<!-- The featured image URL goes in the src attribute -->
{% if "placeholder" in post.featured_image.url %}
<img src="https://codeinstitute.s3.amazonaws.com/fullstack/blog/default.jpg" width="100%">
{% else %}
<img src=" {{ post.featured_image.url }}" width="100%">
{% endif %}
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col card mb-4 mt-3 left top">
<div class="card-body text-dark">
<!-- The post content goes inside the card-text. -->
<!-- Use the | safe filter inside the template tags -->
<p class="card-text text-dark">
{{ post.content | safe }}
</p>
<div class="row">
<div class="col-1">
<strong>
{% if user.is_authenticated %}
<form class="d-inline" action="{% url 'post_like' post.slug %}" method="POST">
{% csrf_token %}
{% if liked %}
<button type="submit" name="blogpost_id" value="{{post.slug}}" class="btn-like"><i class="fas fa-heart"></i></button>
{% else %}
<button type="submit" name="blogpost_id" value="{{post.slug}}" class="btn-like"><i class="far fa-heart"></i></button>
{% endif %}
</form>
{% else %}
<span class="text-secondary"><i class="far fa-heart"></i></span>
{% endif %}
<!-- The number of likes goes before the closing strong tag -->
<span class="text-secondary">{{ post.number_of_likes }} </span>
</strong>
</div>
<div class="col-1">
{% with comments.count as total_comments %}
<strong class="text-dark"><i class="far fa-comments"></i>
<!-- Our total_comments variable goes before the closing strong tag -->
{{ total_comments }}</strong>
{% endwith %}
</div>
<div class="col-1">
<a class="btn btn-outline-danger" href="{% url 'delete_post' post.id %}">Delete It</a>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<hr>
</div>
</div>
<div class="row">
<div class="col-md-8 card mb-4 mt-3 ">
<h3 class="text-dark">Comments:</h3>
<div class="card-body">
<!-- We want a for loop inside the empty control tags to iterate through each comment in comments -->
{% for comment in comments %}
<div class="comments text-dark" style="padding: 10px;">
<p class="font-weight-bold">
<!-- The commenter's name goes here. Check the model if you're not sure what that is -->
{{ comment.name }}
<span class=" text-muted font-weight-normal">
<!-- The comment's created date goes here -->
{{ comment.created_on }}
</span> wrote:
</p>
<!-- The body of the comment goes before the | -->
{{ comment.body | linebreaks }}
</div>
<!-- Our for loop ends here -->
{% endfor %}
</div>
</div>
<div class="col-md-4 card mb-4 mt-3 ">
<div class="card-body">
<!-- For later -->
{% if commented %}
<div class="alert alert-success" role="alert">
Your comment is awaiting approval
</div>
{% else %}
{% if user.is_authenticated %}
<h3 class="text-dark">Leave a comment:</h3>
<p class="text-dark">Posting as: {{ user.username }}</p>
<form class="text-dark" method="post" style="margin-top: 1.3em;">
{{ comment_form | crispy }}
{% csrf_token %}
<button type="submit" class="btn btn-signup btn-lg">Submit</button>
</form>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
{% endblock content %}
Any ideas?
I think it should be model not form_class so:
class Deletepost(LoginRequiredMixin, DeleteView):
model = Post
success_url = reverse_lazy('blog:home')
template_name = 'templates/post.html'
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
#SunderamDubey's answer is correct. The test_func will however not run, since this is method of the UserPassesTestMixin [Django-doc], not LoginRequiredMixin.
But using a test function as is done here is not efficient: it will fetch the same object twice from the database. You can simply restrict the queryset, like:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.views.generic import DeleteView
class DeletePostView(LoginRequiredMixin, DeleteView):
model = Post
success_url = reverse_lazy('blog:home')
template_name = 'templates/post.html'
def get_queryset(self, *args, **kwargs):
return (
super().get_queryset(*args, **kwargs).filter(author=self.request.user)
)
This will do filtering at the database side, and thus return a 404 in case the logged in user is not the author of the Post object you want to delete.
In the template, you will need to make a mini-form to make a POST request, for example:
<form method="post" action="{% url 'delete_post' post.id %}">
{% csrf_token %}
<button class="btn btn-outline-danger" type="submit">Delete</button>
</form>
In my opinion, you should change url to below
'path('delete/int:pk', views.Deletepost.as_view(), name='delete_post'),'
if didn't work you can do this
def delete_post(request, post_id):
post = Post.objects.get(pk=post_id)
post.delete()
return redirect('blog:home')

Django forms not returning result or errors

I am trying to use a Django form for a login page, but the form is returning False whenever I call form.is_valid(). I tried to print the errors in the console and in the HTML file but there's nothing displaying. I tried to get data from the form with form["email"].value() and it's returning None, even when I input data into the email field.
Here's views.py:
def postlogin(request):
form = BootstrapAuthenticationForm(request.POST)
# This prints out None into the console
print(form["email"].value())
if form.is_valid():
# This code never runs! Even when there are no errors in the form fields!
return render(request, "app/home.html")
# If form is invalid, return to login page
# This always runs, meaning form.is_valid() keeps being false!
return render(request,"app/login.html", {"form":form})
Here's my forms.py:
from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import ugettext_lazy as _
class BootstrapAuthenticationForm(AuthenticationForm):
"""Authentication form which uses boostrap CSS."""
email = forms.CharField(max_length=254,label=('Email'),
widget=forms.TextInput({
'class': 'form-control',
'placeholder': 'Email'}))
password = forms.CharField(label=("Password"),
widget=forms.PasswordInput({
'class': 'form-control',
'placeholder':'Password'}))
Here's my login.html:
{% extends "app/layout.html" %}
{% block content %}
<h2>{{ title }}</h2>
<div class="row">
<div class="col-md-8">
<section id="loginForm">
<form action="/postlogin/" method="post" class="form-horizontal">
{% csrf_token %}
<h4>Use a local account to log in.</h4>
<hr />
<div class="form-group">
<label for="id_username" class="col-md-2 control-label">{{form.email.label}}</label>
<div class="col-md-10">
{{ form.email }}
</div>
</div>
<div class="form-group">
<label for="id_password" class="col-md-2 control-label">{{form.password.label}}</label>
<div class="col-md-10">
{{ form.password }}
</div>
</div>
<div class="form-group">
<label for="id_password" class="col-md-2 control-label">{{form.test.label}}</label>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="hidden" name="next" value="/" />
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
<!-- Nothing below shows even when form.is_valid() is false -->
{% if form.errors %}
<p class="validation-summary-errors">Please enter a correct user name and password.</p>
{% endif %}
{% if form.non_field_errors %}
<p class="validation-summary-errors">Please enter a correct user name and password.</p>
{% endif %}
</form>
</section>
</div>
<div class="col-md-4">
<section id="socialLoginForm"></section>
</div>
</div>
<!-- Again, none of this shows in the HTML file even when form.is_valid() is False -->
{% for error_field, error_message in form.errors.items %}
{{ error_field|striptags }}: {{ error_message|striptags }}
{% endfor %}
{% endblock %}
if form.is_valid():
# after validating form you can print field value using (cleaned_data) method
print(form.cleaned_data['email'])
return render(request, "app/home.html")

UpdateWithInlinesView for django-extra-views not working with crispy_forms

I have a Django app with django-extra-views to create and maintain a pair of related models. The set up is as follows:
# models.py
class ModelA(models.Model):
# fields
def get_absolute_url(self):
return reverse('model_a:detail', kwargs={'pk': self.id})
class ModelB(models.Model):
model_a = models.OneToOneField(ModelA, on_delete=models.CASCADE)
# other fields
I have two corresponding views and forms:
# views.py
class ModelBView(InlineFormSetFactory):
model = ModelB
form_class = ModelBForm
prefix = 'model_b'
class ModelACreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateWithInlinesView):
model = ModelA
inlines = [ModelBView, ]
permission_required = ('add_model_a')
template_name = 'apps/model_a/create.html'
form = ModelAForm
fields = '__all__'
def get_success_url(self):
return self.object.get_absolute_url()
class ModelAUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateWithInlinesView):
model = ModelA
inlines = [ModelBView, ]
permission_required = ('change_model_a')
template_name = 'apps/model_a/update.html'
form = ModelAForm
fields = '__all__'
def get_success_url(self):
return self.object.get_absolute_url()
# forms.py
class ModelAForm(forms.ModelForm):
class Meta:
model = ModelA
fields = [
# some fields
]
initial = [...]
class ModelBForm(forms.ModelForm):
class Meta:
model = ModelB
fields = [
# some fields
]
The trouble happens in the template files. The CreateWithInlinesView works, while UpdateWithInlinesView does not, when paired with crispy_forms. Following is the code that DOES NOT work. When I press the 'Save' button, it brings me back to the update page and when I check the details page, things are unchanged:
<form class="form-horizontal" method="POST">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-8 mb-0">
{{ form.name|as_crispy_field }}
</div>
<div class="form-group col-md-4 mb-0">
{{ form.phone|as_crispy_field }}
</div>
</div>
{% for formset in inlines %} {% for addr in formset %}
<div class="form-row">
<div class="form-group col-md-6 mb-0">
{{ addr.field_1|as_crispy_field }}
</div>
<div class="form-group col-md-6 mb-0">
{{ addr.field_2|as_crispy_field }}
</div>
</div>
{% endfor %} {{ formset.management_form }} {% endfor %}
<div class="control-group text-right">
<div class="controls">
<button type="submit" class="btn btn-default btn-person"><i class="fas fa-save"></i> Save</button>
</div>
</div>
</form>
There is a version that DOES WORK. But it involves me not using crispy_forms and is aesthically unappealing. When I press the 'Save' button, it updates and redirects to the "details" page. Below is the code (the difference is the bottom half):
<form class="form-horizontal" method="POST">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-8 mb-0">
{{ form.name|as_crispy_field }}
</div>
<div class="form-group col-md-4 mb-0">
{{ form.phone|as_crispy_field }}
</div>
</div>
{% for formset in inlines %} {{ formset }} {% endfor %}
<div class="control-group text-right">
<div class="controls">
<button type="submit" class="btn btn-default btn-person"><i class="fas fa-save"></i> Save</button>
</div>
</div>
</form>
I don't know how to proceed on this one and would love someone's help. Thank you.
This is basically happening because you didn't include id (hidden field) in the formset.
To check error you can write the below method in UpdateWithInlinesView.
def forms_invalid(self, form, inlines):
for formset in inlines:
for errors in formset.errors:
for _, error in errors.items():
print(error[0])
return self.render_to_response(
self.get_context_data(request=self.request, form=form, inlines=inlines))
This will print
id: 'This field is required'
To get rid of this, you have to include hidden fields as follows:
{% for formset in inlines %}
{{ formset.management_form|crispy }}
{{ formset|as_crispy_errors }}
{% for addr in formset %}
{% for hidden in addr.hidden_fields %}
{{ hidden }}
{% endfor %}
<div class="form-row">
<div class="form-group col-md-6 mb-0">
{{ addr.field_1|as_crispy_field }}
</div>
<div class="form-group col-md-6 mb-0">
{{ addr.field_2|as_crispy_field }}
</div>
</div>
{% endfor %}
{% endfor %}
Or you can try below one also. In this we are directly passing the id field (hidden by default). I am using this one
{% for formset in inlines %}
{{ formset.management_form|crispy }}
{{ formset|as_crispy_errors }}
{% for addr in formset %}
{{ addr.id }}
<div class="form-row">
<div class="form-group col-md-6 mb-0">
{{ addr.field_1|as_crispy_field }}
</div>
<div class="form-group col-md-6 mb-0">
{{ addr.field_2|as_crispy_field }}
</div>
</div>
{% endfor %}
{% endfor %}

django-ckeditor :data not loading in the Form

I'm Using Django-ckeditor as a model form widget.I have been able to able to save data using the model-form.
Now that I want to edit any saved data the form does not load the data onto the CKeditor.
Django 1.10 Python 3.5 Windows 7 64bit
forms.py
class templateform(forms.ModelForm):
class Meta:
model = presciptiontemplates
fields = ['template', 'templateid', ]
widgets = {
'template': CKEditorWidget(),
}
labels = {
"templateid": _("Patient ID"),
}
views.py
def get_template(request, tid):
template = presciptiontemplates.objects.get(templateid=tid)
form = templateform(request.POST, instance=template)
if form.is_valid():
form = form.save(commit=False)
form.patientid = tid
form.save()
else:
form = templateform(request.POST, instance=template)
return render(request, 'presapp/viewtemplate.html',{'form': form})
HTML
<div id="preview-content">
<form method="post" style="margin-top:20px;">
{% csrf_token %}
{{ form.media }}
{{ form|safe }}
<div class="" style="margin-top:20px ;">
<button class="btn waves-effect waves-light left" type="submit">Submit
<i class="material-icons right">add_circle</i
</button>
</form>
</div>
</div>
Change {{ form|safe }} to {{ form.as_p }}
<div id="preview-content">
<form method="post" style="margin-top:20px;">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<div class="" style="margin-top:20px ;">
<button class="btn waves-effect waves-light left" type="submit">Submit
<i class="material-icons right">add_circle</i
</button>
</form>
</div>
</div>

Categories