diff --git a/dcc/consist/admin.py b/dcc/consist/admin.py index ffa3a48..679caa6 100644 --- a/dcc/consist/admin.py +++ b/dcc/consist/admin.py @@ -8,28 +8,42 @@ class ConsistItemInline(SortableInlineAdminMixin, admin.TabularInline): model = ConsistItem min_num = 1 extra = 0 - readonly_fields = ('address', 'type', 'company', 'era') + readonly_fields = ("address", "type", "company", "era") @admin.register(Consist) class ConsistAdmin(admin.ModelAdmin): inlines = (ConsistItemInline,) - readonly_fields = ('creation_time', 'updated_time',) - list_display = ('identifier', 'company', 'era') + readonly_fields = ( + "creation_time", + "updated_time", + ) + list_display = ("identifier", "company", "era") list_filter = list_display search_fields = list_display fieldsets = ( - (None, { - 'fields': ('identifier', - 'consist_address', - 'company', - 'era', - 'notes', - 'tags') - }), - ('Audit', { - 'classes': ('collapse',), - 'fields': ('creation_time', 'updated_time',) - }), + ( + None, + { + "fields": ( + "identifier", + "consist_address", + "company", + "era", + "notes", + "tags", + ) + }, + ), + ( + "Audit", + { + "classes": ("collapse",), + "fields": ( + "creation_time", + "updated_time", + ), + }, + ), ) diff --git a/dcc/consist/apps.py b/dcc/consist/apps.py index 5f3aa65..1cf6149 100644 --- a/dcc/consist/apps.py +++ b/dcc/consist/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class ConsistConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'consist' + default_auto_field = "django.db.models.BigAutoField" + name = "consist" diff --git a/dcc/consist/models.py b/dcc/consist/models.py index 16f4fc5..4673910 100644 --- a/dcc/consist/models.py +++ b/dcc/consist/models.py @@ -6,19 +6,15 @@ from roster.models import RollingStock class Consist(models.Model): - uuid = models.UUIDField( - primary_key=True, default=uuid4, - editable=False) + uuid = models.UUIDField(primary_key=True, default=uuid4, editable=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( - default=None, null=True, blank=True) + default=None, null=True, blank=True + ) company = models.ForeignKey( - Company, on_delete=models.CASCADE, - null=True, blank=True) + Company, on_delete=models.CASCADE, null=True, blank=True + ) era = models.CharField(max_length=32, blank=True) notes = models.TextField(blank=True) creation_time = models.DateTimeField(auto_now_add=True) @@ -29,14 +25,12 @@ class Consist(models.Model): class ConsistItem(models.Model): - consist = models.ForeignKey( - Consist, on_delete=models.CASCADE) - rolling_stock = models.ForeignKey( - RollingStock, on_delete=models.CASCADE) + consist = models.ForeignKey(Consist, on_delete=models.CASCADE) + rolling_stock = models.ForeignKey(RollingStock, on_delete=models.CASCADE) order = models.PositiveIntegerField(default=0, blank=False, null=False) class Meta(object): - ordering = ['order'] + ordering = ["order"] def __str__(self): return "{0}".format(self.rolling_stock) diff --git a/dcc/dcc/asgi.py b/dcc/dcc/asgi.py index 0db25d5..b25b7d7 100644 --- a/dcc/dcc/asgi.py +++ b/dcc/dcc/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dcc.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dcc.settings") application = get_asgi_application() diff --git a/dcc/dcc/parsers.py b/dcc/dcc/parsers.py index f46c972..29755d0 100644 --- a/dcc/dcc/parsers.py +++ b/dcc/dcc/parsers.py @@ -2,7 +2,7 @@ from rest_framework.parsers import BaseParser class PlainTextParser(BaseParser): - media_type = 'text/plain' + media_type = "text/plain" def parse(self, stream, media_type=None, parser_context=None): return stream.read() diff --git a/dcc/dcc/settings.py b/dcc/dcc/settings.py index a1f3046..a87be4f 100644 --- a/dcc/dcc/settings.py +++ b/dcc/dcc/settings.py @@ -21,7 +21,9 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-1fgtf05rwp0qp05@ef@a7%x#o+t6vk6063py=vhdmut0j!8s4u' +SECRET_KEY = ( + "django-insecure-1fgtf05rwp0qp05@ef@a7%x#o+t6vk6063py=vhdmut0j!8s4u" +) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -32,63 +34,63 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'health_check', - 'health_check.db', - 'django_countries', - 'solo', - 'rest_framework', - 'adminsortable2', - 'dcc', - 'driver', - 'metadata', - 'roster', - 'consist', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "health_check", + "health_check.db", + "django_countries", + "solo", + "rest_framework", + "adminsortable2", + "dcc", + "driver", + "metadata", + "roster", + "consist", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", # 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'dcc.urls' +ROOT_URLCONF = "dcc.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'dcc.wsgi.application' +WSGI_APPLICATION = "dcc.wsgi.application" # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -98,16 +100,16 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -115,9 +117,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/4.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -127,26 +129,26 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = "static/" # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -MEDIA_URL = 'media/' -MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_URL = "media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "media") COUNTRIES_OVERRIDE = { - 'ZZ': "Freelance", + "ZZ": "Freelance", } DECODER_INTERFACES = [ - (1, "NEM651"), - (2, "NEM652"), - (3, "PluX"), - (4, "21MTC"), - (5, "Next18/Next18S") + (1, "NEM651"), + (2, "NEM652"), + (3, "PluX"), + (4, "21MTC"), + (5, "Next18/Next18S"), ] ROLLING_STOCK_TYPES = [ @@ -154,5 +156,5 @@ ROLLING_STOCK_TYPES = [ ("car", "Car"), ("railcar", "Railcar"), ("equipment", "Equipment"), - ("other", "Other") + ("other", "Other"), ] diff --git a/dcc/dcc/urls.py b/dcc/dcc/urls.py index fb5b669..69ba47b 100644 --- a/dcc/dcc/urls.py +++ b/dcc/dcc/urls.py @@ -24,10 +24,10 @@ from driver import urls as driver_urls admin.site.site_header = "Trains assets manager" urlpatterns = [ - path('ht/', include('health_check.urls')), - path('admin/', admin.site.urls), - path('api/v1/roster/', include(roster_urls)), - path('api/v1/dcc/', include(driver_urls)), + path("ht/", include("health_check.urls")), + path("admin/", admin.site.urls), + path("api/v1/roster/", include(roster_urls)), + path("api/v1/dcc/", include(driver_urls)), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # if settings.DEBUG: diff --git a/dcc/dcc/utils.py b/dcc/dcc/utils.py index a2b6f59..e97769e 100644 --- a/dcc/dcc/utils.py +++ b/dcc/dcc/utils.py @@ -5,12 +5,13 @@ from django.utils.text import slugify as django_slugify def get_image_preview(url): return format_html( '' % url) + 'background-color: #eee;" />' % url + ) def slugify(string, custom_separator=None): # Make slug 'flat', both '-' and '_' are replaced with '-' - string = django_slugify(string).replace('_', '-') + string = django_slugify(string).replace("_", "-") if custom_separator is not None: - string = string.replace('-', custom_separator) + string = string.replace("-", custom_separator) return string diff --git a/dcc/dcc/wsgi.py b/dcc/dcc/wsgi.py index 8435ef1..4b7e8c5 100644 --- a/dcc/dcc/wsgi.py +++ b/dcc/dcc/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dcc.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dcc.settings") application = get_wsgi_application() diff --git a/dcc/driver/apps.py b/dcc/driver/apps.py index 27eca5a..0c77fc7 100644 --- a/dcc/driver/apps.py +++ b/dcc/driver/apps.py @@ -3,9 +3,10 @@ from health_check.plugins import plugin_dir class DriverConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'driver' + default_auto_field = "django.db.models.BigAutoField" + name = "driver" def ready(self): from driver.health import DriverHealthCheck + plugin_dir.register(DriverHealthCheck) diff --git a/dcc/driver/connector.py b/dcc/driver/connector.py index d195b41..def3c7e 100644 --- a/dcc/driver/connector.py +++ b/dcc/driver/connector.py @@ -8,7 +8,7 @@ class Connector: self.config = DriverConfiguration.get_solo() def __send_data(self, message): - resp = b'' + resp = b"" # convert to binary if str is received if isinstance(message, str): message = message.encode() @@ -30,10 +30,12 @@ class Connector: def ops(self, address, data, function=False): if function: message = "".format( - address, data['function'], data['state']) + address, data["function"], data["state"] + ) else: message = "".format( - address, data['speed'], data['direction']) + address, data["speed"], data["direction"] + ) self.__send_data(message) def infra(self, data): @@ -43,9 +45,9 @@ class Connector: track = "" if data["power"]: - self.__send_data('<1{}>'.format(track)) + self.__send_data("<1{}>".format(track)) else: - self.__send_data('<0{}>'.format(track)) + self.__send_data("<0{}>".format(track)) def emergency(self): - self.__send_data('') + self.__send_data("") diff --git a/dcc/driver/health.py b/dcc/driver/health.py index 75e09f9..b715b1a 100644 --- a/dcc/driver/health.py +++ b/dcc/driver/health.py @@ -1,6 +1,8 @@ from health_check.backends import BaseHealthCheckBackend -from health_check.exceptions import (ServiceUnavailable, - ServiceReturnedUnexpectedResult) +from health_check.exceptions import ( + ServiceUnavailable, + ServiceReturnedUnexpectedResult, +) from driver.connector import Connector @@ -10,7 +12,7 @@ class DriverHealthCheck(BaseHealthCheckBackend): def check_status(self): try: - Connector().passthrough(b'') + Connector().passthrough(b"") except ConnectionRefusedError as e: self.add_error(ServiceUnavailable("IOError"), e) except Exception as e: diff --git a/dcc/driver/models.py b/dcc/driver/models.py index 972ca19..f5a72df 100644 --- a/dcc/driver/models.py +++ b/dcc/driver/models.py @@ -4,7 +4,8 @@ from solo.models import SingletonModel class DriverConfiguration(SingletonModel): remote_host = models.GenericIPAddressField( - protocol="IPv4", default="192.168.4.1") + protocol="IPv4", default="192.168.4.1" + ) remote_port = models.SmallIntegerField(default=2560) timeout = models.SmallIntegerField(default=250) diff --git a/dcc/driver/serializers.py b/dcc/driver/serializers.py index 19b5ac1..68af6ce 100644 --- a/dcc/driver/serializers.py +++ b/dcc/driver/serializers.py @@ -14,5 +14,6 @@ class CabSerializer(serializers.Serializer): class InfraSerializer(serializers.Serializer): power = serializers.BooleanField(required=True) track = serializers.ChoiceField( - choices=('main', 'prog', 'join', 'MAIN', 'PROG', 'JOIN'), - required=False) + choices=("main", "prog", "join", "MAIN", "PROG", "JOIN"), + required=False, + ) diff --git a/dcc/driver/urls.py b/dcc/driver/urls.py index 64a8e26..d8a52f7 100644 --- a/dcc/driver/urls.py +++ b/dcc/driver/urls.py @@ -2,10 +2,10 @@ from django.urls import path from driver.views import SendCommand, Function, Cab, Emergency, Infra, Test urlpatterns = [ - path('test', Test.as_view()), - path('emergency', Emergency.as_view()), - path('infra', Infra.as_view()), - path('command', SendCommand.as_view()), - path('/cab', Cab.as_view()), - path('/function', Function.as_view()), + path("test", Test.as_view()), + path("emergency", Emergency.as_view()), + path("infra", Infra.as_view()), + path("command", SendCommand.as_view()), + path("/cab", Cab.as_view()), + path("/function", Function.as_view()), ] diff --git a/dcc/driver/views.py b/dcc/driver/views.py index 1b9236b..ed51f7a 100644 --- a/dcc/driver/views.py +++ b/dcc/driver/views.py @@ -7,7 +7,10 @@ from rest_framework.response import Response from dcc.parsers import PlainTextParser from driver.connector import Connector from driver.serializers import ( - FunctionSerializer, CabSerializer, InfraSerializer) + FunctionSerializer, + CabSerializer, + InfraSerializer, +) from roster.models import RollingStock @@ -15,10 +18,12 @@ def addresschecker(f): """ Check if DCC address does exist in the database """ + def addresslookup(request, address, *args): if not RollingStock.objects.filter(address=address): raise Http404 return f(request, address, *args) + return addresslookup @@ -26,32 +31,38 @@ class Test(APIView): """ Send a test command """ + parser_classes = [PlainTextParser] def get(self, request): response = Connector().passthrough("") - return Response({"response": response.decode()}, - status=status.HTTP_202_ACCEPTED) + return Response( + {"response": response.decode()}, status=status.HTTP_202_ACCEPTED + ) class SendCommand(APIView): """ Command passthrough """ + parser_classes = [PlainTextParser] def put(self, request): data = request.data if not data: - raise serializers.ValidationError({ - "error": "a string is expected"}) + raise serializers.ValidationError( + {"error": "a string is expected"} + ) cmd = data.decode().strip() if not (cmd.startswith("<") and cmd.endswith(">")): - raise serializers.ValidationError({ - "error": "please provide a valid command"}) + raise serializers.ValidationError( + {"error": "please provide a valid command"} + ) response = Connector().passthrough(cmd) - return Response({"response": response.decode()}, - status=status.HTTP_202_ACCEPTED) + return Response( + {"response": response.decode()}, status=status.HTTP_202_ACCEPTED + ) @method_decorator(addresschecker, name="put") @@ -59,15 +70,14 @@ class Function(APIView): """ Send "Function" commands to a valid DCC address """ + def put(self, request, address): serializer = FunctionSerializer(data=request.data) if serializer.is_valid(): Connector().ops(address, serializer.data, function=True) - return Response(serializer.data, - status=status.HTTP_202_ACCEPTED) + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) - return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @method_decorator(addresschecker, name="put") @@ -75,42 +85,43 @@ class Cab(APIView): """ Send "Cab" commands to a valid DCC address """ + def put(self, request, address): serializer = CabSerializer(data=request.data) if serializer.is_valid(): Connector().ops(address, serializer.data) - return Response(serializer.data, - status=status.HTTP_202_ACCEPTED) + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) - return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class Infra(APIView): """ Send "Infra" commands to a valid DCC address """ + def put(self, request): serializer = InfraSerializer(data=request.data) if serializer.is_valid(): Connector().infra(serializer.data) - return Response(serializer.data, - status=status.HTTP_202_ACCEPTED) + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) - return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class Emergency(APIView): """ Send an "Emergency" stop, no matter the HTTP method used """ + def put(self, request): Connector().emergency() - return Response({"response": "emergency stop"}, - status=status.HTTP_202_ACCEPTED) + return Response( + {"response": "emergency stop"}, status=status.HTTP_202_ACCEPTED + ) def get(self, request): Connector().emergency() - return Response({"response": "emergency stop"}, - status=status.HTTP_202_ACCEPTED) + return Response( + {"response": "emergency stop"}, status=status.HTTP_202_ACCEPTED + ) diff --git a/dcc/manage.py b/dcc/manage.py index 88b1b9e..20ea1dc 100755 --- a/dcc/manage.py +++ b/dcc/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dcc.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dcc.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/dcc/metadata/admin.py b/dcc/metadata/admin.py index 7d01f4e..fdedd1a 100644 --- a/dcc/metadata/admin.py +++ b/dcc/metadata/admin.py @@ -1,40 +1,46 @@ from django.contrib import admin from metadata.models import ( - Decoder, Scale, Manufacturer, Company, Tag, RollingStockType) + Decoder, + Scale, + Manufacturer, + Company, + Tag, + RollingStockType, +) @admin.register(Decoder) class DecoderAdmin(admin.ModelAdmin): - readonly_fields = ('image_thumbnail',) - list_display = ('__str__', 'interface') - list_filter = ('manufacturer', 'interface') + readonly_fields = ("image_thumbnail",) + list_display = ("__str__", "interface") + list_filter = ("manufacturer", "interface") @admin.register(Scale) class ScaleAdmin(admin.ModelAdmin): - list_display = ('scale', 'ratio', 'gauge') - list_filter = ('ratio', 'gauge') + list_display = ("scale", "ratio", "gauge") + list_filter = ("ratio", "gauge") @admin.register(Company) class CompanyAdmin(admin.ModelAdmin): - readonly_fields = ('logo_thumbnail',) - list_display = ('name', 'country') + readonly_fields = ("logo_thumbnail",) + list_display = ("name", "country") list_filter = list_display @admin.register(Manufacturer) class ManufacturerAdmin(admin.ModelAdmin): - readonly_fields = ('logo_thumbnail',) + readonly_fields = ("logo_thumbnail",) @admin.register(Tag) class TagAdmin(admin.ModelAdmin): - readonly_fields = ('slug',) - list_display = ('name', 'slug') + readonly_fields = ("slug",) + list_display = ("name", "slug") @admin.register(RollingStockType) class RollingStockTypeAdmin(admin.ModelAdmin): - list_display = ('__str__',) - list_filter = ('type', 'category') + list_display = ("__str__",) + list_filter = ("type", "category") diff --git a/dcc/metadata/apps.py b/dcc/metadata/apps.py index 1d731c9..e41215d 100644 --- a/dcc/metadata/apps.py +++ b/dcc/metadata/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class MetadataConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'metadata' + default_auto_field = "django.db.models.BigAutoField" + name = "metadata" diff --git a/dcc/metadata/models.py b/dcc/metadata/models.py index 8b2ede8..58ed46d 100644 --- a/dcc/metadata/models.py +++ b/dcc/metadata/models.py @@ -9,16 +9,14 @@ from dcc.utils import get_image_preview, slugify class Manufacturer(models.Model): name = models.CharField(max_length=128, unique=True) website = models.URLField(blank=True) - logo = models.ImageField( - upload_to='images/', - null=True, - blank=True) + logo = models.ImageField(upload_to="images/", null=True, blank=True) def __str__(self): return self.name def logo_thumbnail(self): return get_image_preview(self.logo.url) + logo_thumbnail.short_description = "Preview" @@ -27,45 +25,37 @@ class Company(models.Model): extended_name = models.CharField(max_length=128, blank=True) country = CountryField() freelance = models.BooleanField(default=False) - logo = models.ImageField( - upload_to='images/', - null=True, - blank=True) + logo = models.ImageField(upload_to="images/", null=True, blank=True) class Meta: verbose_name_plural = "Companies" - ordering = ['name'] + ordering = ["name"] def __str__(self): return self.name def logo_thumbnail(self): return get_image_preview(self.logo.url) + logo_thumbnail.short_description = "Preview" class Decoder(models.Model): name = models.CharField(max_length=128, unique=True) - manufacturer = models.ForeignKey( - Manufacturer, - on_delete=models.CASCADE) + manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) version = models.CharField(max_length=64, blank=True) interface = models.PositiveSmallIntegerField( - choices=settings.DECODER_INTERFACES, - null=True, - blank=True + choices=settings.DECODER_INTERFACES, null=True, blank=True ) sound = models.BooleanField(default=False) - image = models.ImageField( - upload_to='images/', - null=True, - blank=True) + image = models.ImageField(upload_to="images/", null=True, blank=True) def __str__(self): return "{0} - {1}".format(self.manufacturer, self.name) def image_thumbnail(self): return get_image_preview(self.image.url) + image_thumbnail.short_description = "Preview" @@ -75,7 +65,7 @@ class Scale(models.Model): gauge = models.CharField(max_length=16, blank=True) class Meta: - ordering = ['scale'] + ordering = ["scale"] def __str__(self): return str(self.scale) @@ -97,10 +87,11 @@ def tag_pre_save(sender, instance, **kwargs): class RollingStockType(models.Model): type = models.CharField(max_length=64) category = models.CharField( - max_length=64, choices=settings.ROLLING_STOCK_TYPES) + max_length=64, choices=settings.ROLLING_STOCK_TYPES + ) class Meta(object): - unique_together = ('category', 'type') + unique_together = ("category", "type") def __str__(self): return "{0} {1}".format(self.type, self.category) diff --git a/dcc/metadata/serializers.py b/dcc/metadata/serializers.py index 596ba2a..7dd3e0b 100644 --- a/dcc/metadata/serializers.py +++ b/dcc/metadata/serializers.py @@ -1,7 +1,12 @@ from rest_framework import serializers from metadata.models import ( - RollingStockType, Scale, Manufacturer, - Company, Decoder, Tag) + RollingStockType, + Scale, + Manufacturer, + Company, + Decoder, + Tag, +) class RollingStockTypeSerializer(serializers.ModelSerializer): diff --git a/dcc/roster/admin.py b/dcc/roster/admin.py index 7acc238..37aff2d 100644 --- a/dcc/roster/admin.py +++ b/dcc/roster/admin.py @@ -1,12 +1,16 @@ from django.contrib import admin from roster.models import ( - RollingClass, RollingStock, RollingStockImage, RollingStockDocument) + RollingClass, + RollingStock, + RollingStockImage, + RollingStockDocument, +) @admin.register(RollingClass) class RollingClass(admin.ModelAdmin): - list_display = ('__str__', 'type', 'company') - list_filter = ('company', 'type__category', 'type') + list_display = ("__str__", "type", "company") + list_filter = ("company", "type__category", "type") search_fields = list_display @@ -20,38 +24,58 @@ class RollingStockImageInline(admin.TabularInline): model = RollingStockImage min_num = 0 extra = 0 - readonly_fields = ('image_thumbnail',) + readonly_fields = ("image_thumbnail",) @admin.register(RollingStock) class RollingStockAdmin(admin.ModelAdmin): inlines = (RollingStockImageInline, RollingStockDocInline) - readonly_fields = ('creation_time', 'updated_time') + readonly_fields = ("creation_time", "updated_time") list_display = ( - '__str__', 'address', 'manufacturer', - 'scale', 'sku', 'company', 'country') + "__str__", + "address", + "manufacturer", + "scale", + "sku", + "company", + "country", + ) list_filter = ( - 'rolling_class__type__category', 'rolling_class__type', - 'scale', 'manufacturer') + "rolling_class__type__category", + "rolling_class__type", + "scale", + "manufacturer", + ) search_fields = list_display fieldsets = ( - (None, { - 'fields': ('rolling_class', - 'road_number', - 'manufacturer', - 'scale', - 'sku', - 'decoder', - 'address', - 'era', - 'production_year', - 'purchase_date', - 'notes', - 'tags') - }), - ('Audit', { - 'classes': ('collapse',), - 'fields': ('creation_time', 'updated_time',) - }), + ( + None, + { + "fields": ( + "rolling_class", + "road_number", + "manufacturer", + "scale", + "sku", + "decoder", + "address", + "era", + "production_year", + "purchase_date", + "notes", + "tags", + ) + }, + ), + ( + "Audit", + { + "classes": ("collapse",), + "fields": ( + "creation_time", + "updated_time", + ), + }, + ), ) diff --git a/dcc/roster/apps.py b/dcc/roster/apps.py index 39f52e7..e0172d1 100644 --- a/dcc/roster/apps.py +++ b/dcc/roster/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class RosterConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'roster' + default_auto_field = "django.db.models.BigAutoField" + name = "roster" diff --git a/dcc/roster/models.py b/dcc/roster/models.py index df1aa80..5c99b03 100644 --- a/dcc/roster/models.py +++ b/dcc/roster/models.py @@ -2,12 +2,19 @@ import os from uuid import uuid4 from django.db import models from django.urls import reverse + # from django.core.files.storage import FileSystemStorage # from django.dispatch import receiver from dcc.utils import get_image_preview from metadata.models import ( - Scale, Manufacturer, Decoder, Company, Tag, RollingStockType) + Scale, + Manufacturer, + Decoder, + Company, + Tag, + RollingStockType, +) # class OverwriteMixin(FileSystemStorage): # def get_available_name(self, name, max_length): @@ -18,15 +25,15 @@ from metadata.models import ( class RollingClass(models.Model): identifier = models.CharField(max_length=128, unique=False) type = models.ForeignKey( - RollingStockType, on_delete=models.CASCADE, - null=True, blank=True) + RollingStockType, on_delete=models.CASCADE, null=True, blank=True + ) description = models.CharField(max_length=256, blank=True) company = models.ForeignKey( - Company, on_delete=models.CASCADE, - null=True, blank=True) + Company, on_delete=models.CASCADE, null=True, blank=True + ) class Meta: - ordering = ['company', 'identifier'] + ordering = ["company", "identifier"] verbose_name = "Class" verbose_name_plural = "Classes" @@ -35,37 +42,36 @@ class RollingClass(models.Model): class RollingStock(models.Model): - uuid = models.UUIDField( - primary_key=True, default=uuid4, - editable=False) + uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False) rolling_class = models.ForeignKey( - RollingClass, on_delete=models.CASCADE, - null=False, blank=False, - verbose_name="Class") + RollingClass, + on_delete=models.CASCADE, + null=False, + blank=False, + verbose_name="Class", + ) road_number = models.CharField(max_length=128, unique=False) manufacturer = models.ForeignKey( - Manufacturer, on_delete=models.CASCADE, - null=True, blank=True) - scale = models.ForeignKey( - Scale, on_delete=models.CASCADE) + Manufacturer, on_delete=models.CASCADE, null=True, blank=True + ) + scale = models.ForeignKey(Scale, on_delete=models.CASCADE) sku = models.CharField(max_length=32, blank=True) decoder = models.ForeignKey( - Decoder, on_delete=models.CASCADE, - null=True, blank=True) + Decoder, on_delete=models.CASCADE, null=True, blank=True + ) address = models.SmallIntegerField(default=None, null=True, blank=True) era = models.CharField(max_length=32, blank=True) production_year = models.SmallIntegerField(null=True, blank=True) purchase_date = models.DateField(null=True, blank=True) notes = models.TextField(blank=True) tags = models.ManyToManyField( - Tag, - related_name='rolling_stock', - blank=True) + Tag, related_name="rolling_stock", blank=True + ) creation_time = models.DateTimeField(auto_now_add=True) updated_time = models.DateTimeField(auto_now=True) class Meta: - ordering = ['rolling_class', 'road_number'] + ordering = ["rolling_class", "road_number"] verbose_name_plural = "Rolling stock" def __str__(self): @@ -79,16 +85,12 @@ class RollingStock(models.Model): class RollingStockDocument(models.Model): - rolling_stock = models.ForeignKey( - RollingStock, on_delete=models.CASCADE) + rolling_stock = models.ForeignKey(RollingStock, on_delete=models.CASCADE) description = models.CharField(max_length=128, blank=True) - file = models.FileField( - upload_to='files/', - null=True, - blank=True) + file = models.FileField(upload_to="files/", null=True, blank=True) class Meta(object): - unique_together = ('rolling_stock', 'file') + unique_together = ("rolling_stock", "file") def __str__(self): return "{0}".format(os.path.basename(self.file.name)) @@ -96,23 +98,21 @@ class RollingStockDocument(models.Model): class RollingStockImage(models.Model): - rolling_stock = models.ForeignKey( - RollingStock, on_delete=models.CASCADE) - image = models.ImageField( - upload_to='images/', - null=True, - blank=True) + rolling_stock = models.ForeignKey(RollingStock, on_delete=models.CASCADE) + image = models.ImageField(upload_to="images/", null=True, blank=True) def image_thumbnail(self): return get_image_preview(self.image.url) + image_thumbnail.short_description = "Preview" class Meta(object): - unique_together = ('rolling_stock', 'image') + unique_together = ("rolling_stock", "image") def __str__(self): return "{0}".format(os.path.basename(self.image.name)) + # @receiver(models.signals.post_delete, sender=Cab) # def post_save_image(sender, instance, *args, **kwargs): # try: diff --git a/dcc/roster/serializers.py b/dcc/roster/serializers.py index 3288ca0..228e43a 100644 --- a/dcc/roster/serializers.py +++ b/dcc/roster/serializers.py @@ -1,8 +1,13 @@ from rest_framework import serializers from roster.models import RollingClass, RollingStock from metadata.serializers import ( - RollingStockTypeSerializer, ManufacturerSerializer, ScaleSerializer, - CompanySerializer, DecoderSerializer, TagSerializer) + RollingStockTypeSerializer, + ManufacturerSerializer, + ScaleSerializer, + CompanySerializer, + DecoderSerializer, + TagSerializer, +) class RollingClassSerializer(serializers.ModelSerializer): diff --git a/dcc/roster/urls.py b/dcc/roster/urls.py index afca8f4..87ccf38 100644 --- a/dcc/roster/urls.py +++ b/dcc/roster/urls.py @@ -1,10 +1,9 @@ from django.urls import path -from roster.views import ( - RosterList, RosterGet, RosterAddress, RosterIdentifier) +from roster.views import RosterList, RosterGet, RosterAddress, RosterIdentifier urlpatterns = [ - path('list', RosterList.as_view()), - path('get/', RosterGet.as_view()), - path('address/', RosterAddress.as_view()), - path('identifier/', RosterIdentifier.as_view()), + path("list", RosterList.as_view()), + path("get/", RosterGet.as_view()), + path("address/", RosterAddress.as_view()), + path("identifier/", RosterIdentifier.as_view()), ] diff --git a/dcc/roster/views.py b/dcc/roster/views.py index 6a29409..a11ac45 100644 --- a/dcc/roster/views.py +++ b/dcc/roster/views.py @@ -12,16 +12,16 @@ class RosterList(ListAPIView): class RosterGet(RetrieveAPIView): queryset = RollingStock.objects.all() serializer_class = RollingStockSerializer - lookup_field = 'uuid' + lookup_field = "uuid" class RosterAddress(ListAPIView): queryset = RollingStock.objects.all() serializer_class = RollingStockSerializer - lookup_field = 'address' + lookup_field = "address" class RosterIdentifier(RetrieveAPIView): queryset = RollingStock.objects.all() serializer_class = RollingStockSerializer - lookup_field = 'identifier' + lookup_field = "identifier"