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,) inlines = (BookImageInline, BookPropertyInline,)
list_display = ( list_display = (
"title", "title",
"published",
"get_authors", "get_authors",
"get_publisher", "get_publisher",
"publication_year", "publication_year",
"number_of_pages" "number_of_pages"
) )
readonly_fields = ("creation_time", "updated_time")
search_fields = ("title", "publisher__name", "authors__last_name") search_fields = ("title", "publisher__name", "authors__last_name")
list_filter = ("publisher__name", "authors") 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") @admin.display(description="Publisher")
def get_publisher(self, obj): def get_publisher(self, obj):
return obj.publisher.name 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 os
import shutil import shutil
from uuid import uuid4
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.urls import reverse from django.urls import reverse
@@ -10,7 +9,7 @@ from tinymce import models as tinymce
from metadata.models import Tag from metadata.models import Tag
from ram.utils import DeduplicatedStorage from ram.utils import DeduplicatedStorage
from ram.models import Image, PropertyInstance from ram.models import BaseModel, Image, PropertyInstance
class Publisher(models.Model): class Publisher(models.Model):
@@ -39,8 +38,7 @@ class Author(models.Model):
return f"{self.last_name} {self.first_name[0]}." return f"{self.last_name} {self.first_name[0]}."
class Book(models.Model): class Book(BaseModel):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
title = models.CharField(max_length=200) title = models.CharField(max_length=200)
authors = models.ManyToManyField(Author) authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE) publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
@@ -57,9 +55,6 @@ class Book(models.Model):
tags = models.ManyToManyField( tags = models.ManyToManyField(
Tag, related_name="bookshelf", blank=True 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: class Meta:
ordering = ["title"] ordering = ["title"]

View File

@@ -3,5 +3,5 @@ from bookshelf.views import BookList, BookGet
urlpatterns = [ urlpatterns = [
path("book/list", BookList.as_view()), 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): class BookList(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer serializer_class = BookSerializer
def get_queryset(self):
return Book.objects.get_published(self.request.user)
class BookGet(RetrieveAPIView): class BookGet(RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer serializer_class = BookSerializer
lookup_field = "uuid" lookup_field = "uuid"
schema = AutoSchema(operation_id_base="retrieveBookByUUID") 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 min_num = 1
extra = 0 extra = 0
autocomplete_fields = ("rolling_stock",) autocomplete_fields = ("rolling_stock",)
readonly_fields = ("preview", "address", "type", "company", "era") readonly_fields = ("preview", "published", "address", "type", "company", "era")
@admin.register(Consist) @admin.register(Consist)
@@ -19,7 +19,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
"creation_time", "creation_time",
"updated_time", "updated_time",
) )
list_display = ("identifier", "company", "era") list_display = ("identifier", "published", "company", "era")
list_filter = list_display list_filter = list_display
search_fields = list_display search_fields = list_display
save_as = True save_as = True
@@ -29,6 +29,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
None, None,
{ {
"fields": ( "fields": (
"published",
"identifier", "identifier",
"consist_address", "consist_address",
"company", "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 import os
from uuid import uuid4
from django.db import models from django.db import models
from django.urls import reverse 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 ram.utils import DeduplicatedStorage
from metadata.models import Company, Tag from metadata.models import Company, Tag
from roster.models import RollingStock from roster.models import RollingStock
class Consist(models.Model): class Consist(BaseModel):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
identifier = models.CharField(max_length=128, unique=False) identifier = models.CharField(max_length=128, unique=False)
tags = models.ManyToManyField(Tag, related_name="consist", blank=True) tags = models.ManyToManyField(Tag, related_name="consist", blank=True)
consist_address = models.SmallIntegerField( consist_address = models.SmallIntegerField(
@@ -33,9 +32,6 @@ class Consist(models.Model):
null=True, null=True,
blank=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): def __str__(self):
return "{0} {1}".format(self.company, self.identifier) return "{0} {1}".format(self.company, self.identifier)
@@ -43,6 +39,12 @@ class Consist(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("consist", kwargs={"uuid": self.uuid}) 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: class Meta:
ordering = ["company", "-creation_time"] ordering = ["company", "-creation_time"]
@@ -60,6 +62,10 @@ class ConsistItem(models.Model):
def __str__(self): def __str__(self):
return "{0}".format(self.rolling_stock) return "{0}".format(self.rolling_stock)
def published(self):
return self.rolling_stock.published
published.boolean = True
def preview(self): def preview(self):
return self.rolling_stock.image.first().image_thumbnail(100) return self.rolling_stock.image.first().image_thumbnail(100)
@@ -74,3 +80,14 @@ class ConsistItem(models.Model):
def era(self): def era(self):
return self.rolling_stock.era 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): class ConsistList(ListAPIView):
queryset = Consist.objects.all()
serializer_class = ConsistSerializer serializer_class = ConsistSerializer
def get_queryset(self):
return Consist.objects.get_published(self.request.user)
class ConsistGet(RetrieveAPIView): class ConsistGet(RetrieveAPIView):
queryset = Consist.objects.all()
serializer_class = ConsistSerializer serializer_class = ConsistSerializer
lookup_field = "uuid" 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.models import Document
from ram.utils import DeduplicatedStorage, get_image_preview, slugify from ram.utils import DeduplicatedStorage, get_image_preview, slugify
from ram.managers import PublicManager
class Property(models.Model): class Property(models.Model):
@@ -23,6 +24,8 @@ class Property(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
objects = PublicManager()
class Manufacturer(models.Model): class Manufacturer(models.Model):
name = models.CharField(max_length=128, unique=True) 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 tinymce import models as tinymce
from ram import __version__ as app_version from ram import __version__ as app_version
from ram.managers import PublicManager
from ram.utils import slugify from ram.utils import slugify
@@ -72,6 +73,8 @@ class Flatpage(models.Model):
) )
) )
objects = PublicManager()
@receiver(models.signals.pre_save, sender=Flatpage) @receiver(models.signals.pre_save, sender=Flatpage)
def tag_pre_save(sender, instance, **kwargs): def tag_pre_save(sender, instance, **kwargs):

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
from ram.utils import git_suffix from ram.utils import git_suffix
__version__ = "0.13.0" __version__ = "0.13.1"
__version__ += git_suffix(__file__) __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 import os
from uuid import uuid4
from django.db import models from django.db import models
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from tinymce import models as tinymce
from ram.utils import DeduplicatedStorage, get_image_preview 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): class Document(models.Model):
@@ -28,6 +44,8 @@ class Document(models.Model):
'<a href="{0}" target="_blank">Link</a>'.format(self.file.url) '<a href="{0}" target="_blank">Link</a>'.format(self.file.url)
) )
objects = PublicManager()
class Image(models.Model): class Image(models.Model):
order = models.PositiveIntegerField(default=0, blank=False, null=False) order = models.PositiveIntegerField(default=0, blank=False, null=False)
@@ -48,6 +66,8 @@ class Image(models.Model):
abstract = True abstract = True
ordering = ["order"] ordering = ["order"]
objects = PublicManager()
class PropertyInstance(models.Model): class PropertyInstance(models.Model):
property = models.ForeignKey( property = models.ForeignKey(
@@ -62,3 +82,5 @@ class PropertyInstance(models.Model):
class Meta: class Meta:
abstract = True abstract = True
verbose_name_plural = "Properties" verbose_name_plural = "Properties"
objects = PublicManager()

View File

@@ -111,6 +111,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
readonly_fields = ("preview", "creation_time", "updated_time") readonly_fields = ("preview", "creation_time", "updated_time")
list_display = ( list_display = (
"__str__", "__str__",
"published",
"address", "address",
"manufacturer", "manufacturer",
"scale", "scale",
@@ -141,6 +142,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
{ {
"fields": ( "fields": (
"preview", "preview",
"published",
"rolling_class", "rolling_class",
"road_number", "road_number",
"scale", "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 os
import re import re
import shutil import shutil
from uuid import uuid4
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.conf import settings from django.conf import settings
@@ -9,8 +8,9 @@ from django.dispatch import receiver
from tinymce import models as tinymce 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.utils import DeduplicatedStorage
from ram.managers import PublicManager
from metadata.models import ( from metadata.models import (
Scale, Scale,
Manufacturer, Manufacturer,
@@ -54,8 +54,7 @@ class RollingClassProperty(PropertyInstance):
) )
class RollingStock(models.Model): class RollingStock(BaseModel):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
rolling_class = models.ForeignKey( rolling_class = models.ForeignKey(
RollingClass, RollingClass,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@@ -106,9 +105,6 @@ class RollingStock(models.Model):
tags = models.ManyToManyField( tags = models.ManyToManyField(
Tag, related_name="rolling_stock", blank=True 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: class Meta:
ordering = ["rolling_class", "road_number_int"] ordering = ["rolling_class", "road_number_int"]
@@ -210,6 +206,8 @@ class RollingStockJournal(models.Model):
class Meta: class Meta:
ordering = ["date", "rolling_stock"] ordering = ["date", "rolling_stock"]
objects = PublicManager()
# @receiver(models.signals.post_delete, sender=Cab) # @receiver(models.signals.post_delete, sender=Cab)
# def post_save_image(sender, instance, *args, **kwargs): # def post_save_image(sender, instance, *args, **kwargs):

View File

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

View File

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