Add a custom manager to filter private and unpublished stuff (#39)

* Implement a customer manager for flatpages

* Implement public manager for private objects

* Add support for unpublished objects in roster and consist

* Add support for unpublished objects in bookshelf

* Update filtering on REST views

* Use uuid in urls.py

* Increment version
This commit is contained in:
2024-11-04 15:00:34 +01:00
committed by GitHub
parent 61b6d7a84e
commit 1c07c6a7a9
22 changed files with 297 additions and 154 deletions

View File

@@ -24,14 +24,48 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
inlines = (BookImageInline, BookPropertyInline,)
list_display = (
"title",
"published",
"get_authors",
"get_publisher",
"publication_year",
"number_of_pages"
)
readonly_fields = ("creation_time", "updated_time")
search_fields = ("title", "publisher__name", "authors__last_name")
list_filter = ("publisher__name", "authors")
fieldsets = (
(
None,
{
"fields": (
"published",
"title",
"authors",
"publisher",
"ISBN",
"language",
"number_of_pages",
"publication_year",
"description",
"purchase_date",
"notes",
"tags",
)
},
),
(
"Audit",
{
"classes": ("collapse",),
"fields": (
"creation_time",
"updated_time",
),
},
),
)
@admin.display(description="Publisher")
def get_publisher(self, obj):
return obj.publisher.name

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-11-04 13:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0013_book_description"),
]
operations = [
migrations.AddField(
model_name="book",
name="published",
field=models.BooleanField(default=True),
),
]

View File

@@ -1,6 +1,5 @@
import os
import shutil
from uuid import uuid4
from django.db import models
from django.conf import settings
from django.urls import reverse
@@ -10,7 +9,7 @@ from tinymce import models as tinymce
from metadata.models import Tag
from ram.utils import DeduplicatedStorage
from ram.models import Image, PropertyInstance
from ram.models import BaseModel, Image, PropertyInstance
class Publisher(models.Model):
@@ -39,8 +38,7 @@ class Author(models.Model):
return f"{self.last_name} {self.first_name[0]}."
class Book(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
class Book(BaseModel):
title = models.CharField(max_length=200)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
@@ -57,9 +55,6 @@ class Book(models.Model):
tags = models.ManyToManyField(
Tag, related_name="bookshelf", blank=True
)
notes = tinymce.HTMLField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["title"]

View File

@@ -3,5 +3,5 @@ from bookshelf.views import BookList, BookGet
urlpatterns = [
path("book/list", BookList.as_view()),
path("book/get/<str:uuid>", BookGet.as_view()),
path("book/get/<uuid:uuid>", BookGet.as_view()),
]

View File

@@ -6,13 +6,16 @@ from bookshelf.serializers import BookSerializer
class BookList(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_queryset(self):
return Book.objects.get_published(self.request.user)
class BookGet(RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_field = "uuid"
schema = AutoSchema(operation_id_base="retrieveBookByUUID")
def get_queryset(self):
return Book.objects.get_published(self.request.user)

View File

@@ -9,7 +9,7 @@ class ConsistItemInline(SortableInlineAdminMixin, admin.TabularInline):
min_num = 1
extra = 0
autocomplete_fields = ("rolling_stock",)
readonly_fields = ("preview", "address", "type", "company", "era")
readonly_fields = ("preview", "published", "address", "type", "company", "era")
@admin.register(Consist)
@@ -19,7 +19,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
"creation_time",
"updated_time",
)
list_display = ("identifier", "company", "era")
list_display = ("identifier", "published", "company", "era")
list_filter = list_display
search_fields = list_display
save_as = True
@@ -29,6 +29,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
None,
{
"fields": (
"published",
"identifier",
"consist_address",
"company",

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-11-04 12:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("consist", "0011_alter_consist_consist_address_alter_consist_era"),
]
operations = [
migrations.AddField(
model_name="consist",
name="published",
field=models.BooleanField(default=True),
),
]

View File

@@ -1,18 +1,17 @@
import os
from uuid import uuid4
from django.db import models
from django.urls import reverse
from django.dispatch import receiver
from django.core.exceptions import ValidationError
from tinymce import models as tinymce
from ram.models import BaseModel
from ram.utils import DeduplicatedStorage
from metadata.models import Company, Tag
from roster.models import RollingStock
class Consist(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
class Consist(BaseModel):
identifier = models.CharField(max_length=128, unique=False)
tags = models.ManyToManyField(Tag, related_name="consist", blank=True)
consist_address = models.SmallIntegerField(
@@ -33,9 +32,6 @@ class Consist(models.Model):
null=True,
blank=True,
)
notes = tinymce.HTMLField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
def __str__(self):
return "{0} {1}".format(self.company, self.identifier)
@@ -43,6 +39,12 @@ class Consist(models.Model):
def get_absolute_url(self):
return reverse("consist", kwargs={"uuid": self.uuid})
def clean(self):
if self.consist_item.filter(rolling_stock__published=False).exists():
raise ValidationError(
"You must publish all items in the consist before publishing the consist." # noqa: E501
)
class Meta:
ordering = ["company", "-creation_time"]
@@ -60,6 +62,10 @@ class ConsistItem(models.Model):
def __str__(self):
return "{0}".format(self.rolling_stock)
def published(self):
return self.rolling_stock.published
published.boolean = True
def preview(self):
return self.rolling_stock.image.first().image_thumbnail(100)
@@ -74,3 +80,14 @@ class ConsistItem(models.Model):
def era(self):
return self.rolling_stock.era
# Unpublish any consist that contains an unpublished rolling stock
# this signal is called after a rolling stock is saved
# it is hosted here to avoid circular imports
@receiver(models.signals.post_save, sender=RollingStock)
def post_save_unpublish_consist(sender, instance, *args, **kwargs):
consists = Consist.objects.filter(consist_item__rolling_stock=instance)
for consist in consists:
consist.published = False
consist.save()

View File

@@ -5,11 +5,15 @@ from consist.serializers import ConsistSerializer
class ConsistList(ListAPIView):
queryset = Consist.objects.all()
serializer_class = ConsistSerializer
def get_queryset(self):
return Consist.objects.get_published(self.request.user)
class ConsistGet(RetrieveAPIView):
queryset = Consist.objects.all()
serializer_class = ConsistSerializer
lookup_field = "uuid"
def get_queryset(self):
return Consist.objects.get_published(self.request.user)

View File

@@ -7,6 +7,7 @@ from django_countries.fields import CountryField
from ram.models import Document
from ram.utils import DeduplicatedStorage, get_image_preview, slugify
from ram.managers import PublicManager
class Property(models.Model):
@@ -23,6 +24,8 @@ class Property(models.Model):
def __str__(self):
return self.name
objects = PublicManager()
class Manufacturer(models.Model):
name = models.CharField(max_length=128, unique=True)

View File

@@ -9,6 +9,7 @@ from solo.models import SingletonModel
from tinymce import models as tinymce
from ram import __version__ as app_version
from ram.managers import PublicManager
from ram.utils import slugify
@@ -72,6 +73,8 @@ class Flatpage(models.Model):
)
)
objects = PublicManager()
@receiver(models.signals.pre_save, sender=Flatpage)
def tag_pre_save(sender, instance, **kwargs):

View File

@@ -186,7 +186,7 @@
</ul>
</li>
{% show_bookshelf_menu %}
{% show_flatpages_menu %}
{% show_flatpages_menu user %}
</ul>
{% include 'includes/search.html' %}
</div>

View File

@@ -11,6 +11,6 @@ def show_bookshelf_menu():
@register.inclusion_tag('flatpages/flatpages_menu.html')
def show_flatpages_menu():
menu = Flatpage.objects.filter(published=True).order_by("name")
def show_flatpages_menu(user):
menu = Flatpage.objects.get_published(user).order_by("name")
return {"flatpages_menu": menu}

View File

@@ -17,7 +17,11 @@ from roster.models import RollingStock
from consist.models import Consist
from bookshelf.models import Book
from metadata.models import (
Company, Manufacturer, Scale, DecoderDocument, RollingStockType, Tag
Company,
Manufacturer,
Scale,
RollingStockType,
Tag,
)
@@ -52,11 +56,7 @@ def get_order_by_field():
class Render404(View):
def get(self, request, exception):
return render(
request,
"base.html",
{"title": "404 page not found"}
)
return render(request, "base.html", {"title": "404 page not found"})
class GetData(View):
@@ -65,18 +65,17 @@ class GetData(View):
item_type = "rolling_stock"
filter = Q() # empty filter by default
def get_data(self):
return RollingStock.objects.order_by(
*get_order_by_field()
).filter(self.filter)
def get_data(self, request):
return (
RollingStock.objects.get_published(request.user)
.order_by(*get_order_by_field())
.filter(self.filter)
)
def get(self, request, page=1):
data = []
for item in self.get_data():
data.append({
"type": self.item_type,
"item": item
})
for item in self.get_data(request):
data.append({"type": self.item_type, "item": item})
paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page)
@@ -101,8 +100,10 @@ class GetRoster(GetData):
title = "Roster"
item_type = "rolling_stock"
def get_data(self):
return RollingStock.objects.order_by(*get_order_by_field())
def get_data(self, request):
return RollingStock.objects.get_published(request.user).order_by(
*get_order_by_field()
)
class SearchObjects(View):
@@ -149,18 +150,17 @@ class SearchObjects(View):
# FIXME duplicated code!
data = []
rolling_stock = (
RollingStock.objects.filter(query)
RollingStock.objects.get_published(request.user)
.filter(query)
.distinct()
.order_by(*get_order_by_field())
)
for item in rolling_stock:
data.append({
"type": "rolling_stock",
"item": item
})
data.append({"type": "rolling_stock", "item": item})
if _filter is None:
consists = (
Consist.objects.filter(
Consist.objects.get_published(request.user)
.filter(
Q(
Q(identifier__icontains=search)
| Q(company__name__icontains=search)
@@ -169,19 +169,14 @@ class SearchObjects(View):
.distinct()
)
for item in consists:
data.append({
"type": "consist",
"item": item
})
data.append({"type": "consist", "item": item})
books = (
Book.objects.filter(title__icontains=search)
Book.objects.get_published(request.user)
.filter(title__icontains=search)
.distinct()
)
for item in books:
data.append({
"type": "book",
"item": item
})
data.append({"type": "book", "item": item})
paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page)
@@ -211,8 +206,7 @@ class SearchObjects(View):
encoded_search = search
search = base64.b64decode(search.encode()).decode()
except Exception:
encoded_search = base64.b64encode(
search.encode()).decode()
encoded_search = base64.b64encode(search.encode()).decode()
_filter, keyword = self.split_search(search)
data, matches, page_range = self.run_search(
request, keyword, _filter, page
@@ -222,7 +216,7 @@ class SearchObjects(View):
request,
"search.html",
{
"title": "Search: \"{}\"".format(search),
"title": 'Search: "{}"'.format(search),
"search": search,
"encoded_search": encoded_search,
"data": data,
@@ -239,27 +233,30 @@ class SearchObjects(View):
class GetManufacturerItem(View):
def get(self, request, manufacturer, search="all", page=1):
manufacturer = get_object_or_404(
Manufacturer,
slug__iexact=manufacturer
Manufacturer, slug__iexact=manufacturer
)
if search != "all":
rolling_stock = get_list_or_404(
RollingStock.objects.order_by(*get_order_by_field()),
RollingStock.objects.get_published(request.user).order_by(
*get_order_by_field()
),
Q(
Q(manufacturer=manufacturer)
& Q(item_number_slug__exact=search)
)
),
)
title = "{0}: {1}".format(
manufacturer,
# all returned records must have the same `item_number``;
# just pick it up the first result, otherwise `search`
rolling_stock[0].item_number if rolling_stock else search
rolling_stock[0].item_number if rolling_stock else search,
)
else:
rolling_stock = (
RollingStock.objects.order_by(*get_order_by_field()).filter(
RollingStock.objects.get_published(request.user)
.order_by(*get_order_by_field())
.filter(
Q(manufacturer=manufacturer)
| Q(rolling_class__manufacturer=manufacturer)
)
@@ -268,10 +265,7 @@ class GetManufacturerItem(View):
data = []
for item in rolling_stock:
data.append({
"type": "rolling_stock",
"item": item
})
data.append({"type": "rolling_stock", "item": item})
paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page)
@@ -315,38 +309,32 @@ class GetObjectsFiltered(View):
raise Http404
rolling_stock = (
RollingStock.objects.filter(query)
RollingStock.objects.get_published(request.user)
.filter(query)
.distinct()
.order_by(*get_order_by_field())
)
data = []
for item in rolling_stock:
data.append({
"type": "rolling_stock",
"item": item
})
data.append({"type": "rolling_stock", "item": item})
try: # Execute only if query_2nd is defined
consists = (
Consist.objects.filter(query_2nd)
Consist.objects.get_published(request.user)
.filter(query_2nd)
.distinct()
)
for item in consists:
data.append({
"type": "consist",
"item": item
})
data.append({"type": "consist", "item": item})
if _filter == "tag": # Books can be filtered only by tag
books = (
Book.objects.filter(query_2nd)
Book.objects.get_published(request.user)
.filter(query_2nd)
.distinct()
)
for item in books:
data.append({
"type": "book",
"item": item
})
data.append({"type": "book", "item": item})
except NameError:
pass
@@ -367,8 +355,7 @@ class GetObjectsFiltered(View):
request,
"filter.html",
{
"title": "{0}: {1}".format(
_filter.capitalize(), title),
"title": "{0}: {1}".format(_filter.capitalize(), title),
"search": search,
"filter": _filter,
"data": data,
@@ -381,50 +368,44 @@ class GetObjectsFiltered(View):
class GetRollingStock(View):
def get(self, request, uuid):
try:
rolling_stock = RollingStock.objects.get(uuid=uuid)
rolling_stock = RollingStock.objects.get_published(
request.user
).get(uuid=uuid)
except ObjectDoesNotExist:
raise Http404
# FIXME there's likely a better and more efficient way of doing this
# but keeping KISS for now
decoder_documents = []
if request.user.is_authenticated:
class_properties = rolling_stock.rolling_class.property.all()
properties = rolling_stock.property.all()
documents = rolling_stock.document.all()
journal = rolling_stock.journal.all()
if rolling_stock.decoder:
decoder_documents = rolling_stock.decoder.document.all()
else:
class_properties = rolling_stock.rolling_class.property.filter(
property__private=False
class_properties = rolling_stock.rolling_class.property.get_public(
request.user
)
properties = rolling_stock.property.get_public(request.user)
documents = rolling_stock.document.get_public(request.user)
journal = rolling_stock.journal.get_public(request.user)
if rolling_stock.decoder:
decoder_documents = rolling_stock.decoder.document.get_public(
request.user
)
properties = rolling_stock.property.filter(
property__private=False
consists = [
{"type": "consist", "item": c}
for c in Consist.objects.get_published(request.user).filter(
consist_item__rolling_stock=rolling_stock
)
documents = rolling_stock.document.filter(private=False)
journal = rolling_stock.journal.filter(private=False)
if rolling_stock.decoder:
decoder_documents = rolling_stock.decoder.document.filter(
private=False
)
] # A dict with "item" is required by the consists card
consists = [{
"type": "consist",
"item": c
} for c in Consist.objects.filter(
consist_item__rolling_stock=rolling_stock
)] # A dict with "item" is required by the consists card
set = [{
"type": "set",
"item": s
} for s in RollingStock.objects.filter(
set = [
{"type": "set", "item": s}
for s in RollingStock.objects.get_published(request.user)
.filter(
Q(
Q(item_number__exact=rolling_stock.item_number)
& Q(set=True)
)
).order_by(*get_order_by_field())]
)
.order_by(*get_order_by_field())
]
return render(
request,
@@ -447,20 +428,27 @@ class Consists(GetData):
title = "Consists"
item_type = "consist"
def get_data(self):
return Consist.objects.all()
def get_data(self, request):
return Consist.objects.get_published(request.user).all()
class GetConsist(View):
def get(self, request, uuid, page=1):
try:
consist = Consist.objects.get(uuid=uuid)
consist = Consist.objects.get_published(request.user).get(
uuid=uuid
)
except ObjectDoesNotExist:
raise Http404
data = [{
"type": "rolling_stock",
"item": RollingStock.objects.get(uuid=r.rolling_stock_id)
} for r in consist.consist_item.all()]
data = [
{
"type": "rolling_stock",
"item": RollingStock.objects.get_published(request.user).get(
uuid=r.rolling_stock_id
),
}
for r in consist.consist_item.all()
]
paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page)
@@ -525,22 +513,18 @@ class Books(GetData):
title = "Books"
item_type = "book"
def get_data(self):
return Book.objects.all()
def get_data(self, request):
return Book.objects.get_published(request.user).all()
class GetBook(View):
def get(self, request, uuid):
try:
book = Book.objects.get(uuid=uuid)
book = Book.objects.get_published(request.user).get(uuid=uuid)
except ObjectDoesNotExist:
raise Http404
book_properties = (
book.property.all()
if request.user.is_authenticated
else book.property.filter(property__private=False)
)
book_properties = book.property.get_public(request.user)
return render(
request,
"bookshelf/book.html",
@@ -554,12 +538,10 @@ class GetBook(View):
class GetFlatpage(View):
def get(self, request, flatpage):
_filter = Q(published=True) # Show only published pages
if request.user.is_authenticated:
_filter = Q() # Reset the filter if user is authenticated
try:
flatpage = Flatpage.objects.filter(_filter).get(path=flatpage)
flatpage = Flatpage.objects.get_published(request.user).get(
path=flatpage
)
except ObjectDoesNotExist:
raise Http404

View File

@@ -1,4 +1,4 @@
from ram.utils import git_suffix
__version__ = "0.13.0"
__version__ = "0.13.1"
__version__ += git_suffix(__file__)

19
ram/ram/managers.py Normal file
View File

@@ -0,0 +1,19 @@
from django.db import models
from django.core.exceptions import FieldError
class PublicManager(models.Manager):
def get_published(self, user):
if user.is_authenticated:
return self.get_queryset()
else:
return self.get_queryset().filter(published=True)
def get_public(self, user):
if user.is_authenticated:
return self.get_queryset()
else:
try:
return self.get_queryset().filter(private=False)
except FieldError:
return self.get_queryset().filter(property__private=False)

View File

@@ -1,9 +1,25 @@
import os
from uuid import uuid4
from django.db import models
from django.utils.safestring import mark_safe
from tinymce import models as tinymce
from ram.utils import DeduplicatedStorage, get_image_preview
from ram.managers import PublicManager
class BaseModel(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
notes = tinymce.HTMLField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
published = models.BooleanField(default=True)
class Meta:
abstract = True
objects = PublicManager()
class Document(models.Model):
@@ -28,6 +44,8 @@ class Document(models.Model):
'<a href="{0}" target="_blank">Link</a>'.format(self.file.url)
)
objects = PublicManager()
class Image(models.Model):
order = models.PositiveIntegerField(default=0, blank=False, null=False)
@@ -48,6 +66,8 @@ class Image(models.Model):
abstract = True
ordering = ["order"]
objects = PublicManager()
class PropertyInstance(models.Model):
property = models.ForeignKey(
@@ -62,3 +82,5 @@ class PropertyInstance(models.Model):
class Meta:
abstract = True
verbose_name_plural = "Properties"
objects = PublicManager()

View File

@@ -111,6 +111,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
readonly_fields = ("preview", "creation_time", "updated_time")
list_display = (
"__str__",
"published",
"address",
"manufacturer",
"scale",
@@ -141,6 +142,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
{
"fields": (
"preview",
"published",
"rolling_class",
"road_number",
"scale",

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-11-04 12:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("roster", "0027_alter_rollingstock_decoder_interface"),
]
operations = [
migrations.AddField(
model_name="rollingstock",
name="published",
field=models.BooleanField(default=True),
),
]

View File

@@ -1,7 +1,6 @@
import os
import re
import shutil
from uuid import uuid4
from django.db import models
from django.urls import reverse
from django.conf import settings
@@ -9,8 +8,9 @@ from django.dispatch import receiver
from tinymce import models as tinymce
from ram.models import Document, Image, PropertyInstance
from ram.models import BaseModel, Document, Image, PropertyInstance
from ram.utils import DeduplicatedStorage
from ram.managers import PublicManager
from metadata.models import (
Scale,
Manufacturer,
@@ -54,8 +54,7 @@ class RollingClassProperty(PropertyInstance):
)
class RollingStock(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
class RollingStock(BaseModel):
rolling_class = models.ForeignKey(
RollingClass,
on_delete=models.CASCADE,
@@ -106,9 +105,6 @@ class RollingStock(models.Model):
tags = models.ManyToManyField(
Tag, related_name="rolling_stock", blank=True
)
notes = tinymce.HTMLField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["rolling_class", "road_number_int"]
@@ -210,6 +206,8 @@ class RollingStockJournal(models.Model):
class Meta:
ordering = ["date", "rolling_stock"]
objects = PublicManager()
# @receiver(models.signals.post_delete, sender=Cab)
# def post_save_image(sender, instance, *args, **kwargs):

View File

@@ -3,7 +3,7 @@ from roster.views import RosterList, RosterGet, RosterAddress, RosterClass
urlpatterns = [
path("list", RosterList.as_view()),
path("get/<str:uuid>", RosterGet.as_view()),
path("get/<uuid:uuid>", RosterGet.as_view()),
path("address/<int:address>", RosterAddress.as_view()),
path("class/<str:class>", RosterClass.as_view()),
]

View File

@@ -6,26 +6,30 @@ from roster.serializers import RollingStockSerializer
class RosterList(ListAPIView):
queryset = RollingStock.objects.all()
serializer_class = RollingStockSerializer
def get_queryset(self):
return RollingStock.objects.get_published(self.request.user)
class RosterGet(RetrieveAPIView):
queryset = RollingStock.objects.all()
serializer_class = RollingStockSerializer
lookup_field = "uuid"
schema = AutoSchema(operation_id_base="retrieveRollingStockByUUID")
def get_queryset(self):
return RollingStock.objects.get_published(self.request.user)
class RosterAddress(ListAPIView):
serializer_class = RollingStockSerializer
schema = AutoSchema(operation_id_base="retrieveRollingStockByAddress")
def get_queryset(self):
address = self.kwargs["address"]
return RollingStock.objects.filter(address=address)
return RollingStock.objects.get_published(self.request.user).filter(
address=address
)
class RosterClass(ListAPIView):
@@ -35,4 +39,6 @@ class RosterClass(ListAPIView):
def get_queryset(self):
_class = self.kwargs["class"]
return RollingStock.objects.filter(rolling_class__identifier=_class)
return RollingStock.objects.get_published(self.request.user).filter(
rolling_class__identifier=_class
)