Compare commits
	
		
			13 Commits
		
	
	
		
			39e40d62f4
			...
			c19f4a8159
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c19f4a8159 | |||
| 26b001d983 | |||
| f202896c92 | |||
| 50cfe0a2c6 | |||
| 2d718c5e3a | |||
| 57b475cbe1 | |||
| 25b592ee39 | |||
| 08a5f97fd4 | |||
| 511dacf54a | |||
| 0c4f1f9dba | |||
| b873b1fd0f | |||
| 6e51085210 | |||
| 5163834de4 | 
| @@ -1 +1,2 @@ | ||||
| start_server.sh | ||||
| start_server.sh | ||||
| run/* | ||||
|   | ||||
| @@ -2,6 +2,12 @@ | ||||
| # Example configuration. Must be edited and copied to ".env" next to the compose.yaml | ||||
| #################################################################################################### | ||||
|  | ||||
| # User id to use for the web application. This determines the user id, the media and static files are written to the volumes. | ||||
| # Make sure the user has rw access to these directories. | ||||
| DJANGO_USER_ID=1000 | ||||
|  | ||||
| # Group id to use for the web application | ||||
| DJANGO_USER_GID=1000 | ||||
|  | ||||
| # Path to to mount as the directory for static data. Must be served by a webserver on the /static path | ||||
| DJANGO_STATIC_VOL=/path/to/static/root | ||||
| @@ -29,4 +35,4 @@ DJANGO_MEDIA_URL=media.lab.example.com/ | ||||
|  | ||||
| # Set this password if you want to use a custom postgres password. The db should be confined inside the docker network. | ||||
| # Using the standard PW is therefore not a problem | ||||
| # DJANGO_POSTGRESQL_PW=myfancynewpassword123donotsharemewithanyone | ||||
| # DJANGO_POSTGRESQL_PW=myfancynewpassword123donotsharemewithanyone | ||||
|   | ||||
| @@ -1,6 +1,11 @@ | ||||
| x-op-restart-policy: &restart_policy | ||||
|   restart: unless-stopped | ||||
|  | ||||
| services: | ||||
|   shimatta-kenkyusho-web: | ||||
|     <<: *restart_policy | ||||
|     build: . | ||||
|     user: "${DJANGO_USER_ID}:${DJANGO_USER_GID}" | ||||
|     volumes: | ||||
|       - "${DJANGO_STATIC_VOL:-./run/static}:/var/static" | ||||
|       - "${DJANGO_MEDIA_VOL:-./run/media}:/var/media" | ||||
| @@ -30,6 +35,7 @@ services: | ||||
|       start_period: 30s | ||||
|  | ||||
|   shimatta-kenkyusho-db: | ||||
|     <<: *restart_policy | ||||
|     image: postgres:16.5-alpine | ||||
|     environment: | ||||
|       POSTGRES_PASSWORD: "${DJANGO_POSTGRESQL_PW:-p4ssw0rd}" | ||||
|   | ||||
| @@ -3,4 +3,6 @@ source /home/shimatta/kenkyusho/.venv/bin/activate | ||||
| cd /home/shimatta/kenkyusho/shimatta_kenkyusho | ||||
| python manage.py migrate --settings shimatta_kenkyusho.settings_production | ||||
| python manage.py collectstatic --settings shimatta_kenkyusho.settings_production --noinput | ||||
| python manage.py create_kenkyusho_admin_user --settings shimatta_kenkyusho.settings_production | ||||
|  | ||||
| gunicorn -w 4 --bind 0.0.0.0:8000 shimatta_kenkyusho.wsgi:application | ||||
|   | ||||
| @@ -2,5 +2,6 @@ | ||||
| source /home/shimatta/kenkyusho/.venv/bin/activate | ||||
| cd /home/shimatta/kenkyusho/shimatta_kenkyusho | ||||
| python manage.py migrate --settings shimatta_kenkyusho.settings_production | ||||
| python manage.py create_kenkyusho_admin_user --settings shimatta_kenkyusho.settings_production | ||||
|  | ||||
| python manage.py runserver 0.0.0.0:8000 --settings shimatta_kenkyusho.settings_production | ||||
|   | ||||
| @@ -31,5 +31,6 @@ setuptools==75.3.0 | ||||
| sqlparse==0.4.1 | ||||
| toml==0.10.2 | ||||
| typing_extensions==4.12.2 | ||||
| tzdata==2024.2 | ||||
| urllib3==2.2.3 | ||||
| wrapt==1.12.1 | ||||
|   | ||||
| @@ -252,15 +252,19 @@ class DistributorNumberDeleteForm(forms.Form): | ||||
|  | ||||
| class ComponentParameterDeleteForm(forms.Form): | ||||
|     param_num = forms.UUIDField(required=True) | ||||
|     model = parts_models.ComponentParameter | ||||
|      | ||||
|     def clean_param_num(self): | ||||
|         my_uuid = self.cleaned_data['param_num'] | ||||
|         try: | ||||
|             param = parts_models.ComponentParameter.objects.get(id=my_uuid) | ||||
|             param = self.model.objects.get(id=my_uuid) | ||||
|         except: | ||||
|             raise ValidationError('Parameter Number Invalid') | ||||
|         return param | ||||
|  | ||||
| class PackageParameterDeleteForm(ComponentParameterDeleteForm): | ||||
|     model = parts_models.PackageParameter | ||||
|  | ||||
| class AdvancedComponentSearchForm(forms.Form): | ||||
|     name = forms.CharField(max_length=255, label='Component Name', required=False) | ||||
|     package = AutocompleteForeingKeyField(required=False, api_search_url='package-list', foreign_model=parts_models.Package) | ||||
| @@ -309,6 +313,7 @@ class ComponentParameterSearchForm(forms.Form): | ||||
| class ComponentParameterCreateForm(forms.Form): | ||||
|     parameter_type = AutocompleteForeingKeyField(required=True, foreign_model=parts_models.ComponentParameterType, api_search_url='componentparametertype-list', image_field_name=None, name_field_name='descriptive_name') | ||||
|     value = forms.CharField(required=True, max_length=256) | ||||
|     model = parts_models.ComponentParameter | ||||
|  | ||||
|     def clean(self): | ||||
|         data = super().clean() | ||||
| @@ -338,7 +343,20 @@ class ComponentParameterCreateForm(forms.Form): | ||||
|         else: | ||||
|             text_value = '' | ||||
|             value = self.cleaned_data['number_value'] | ||||
|         parts_models.ComponentParameter.objects.create(parameter_type=param_type, component=component, value=value, text_value=text_value) | ||||
|         self.model.objects.create(parameter_type=param_type, component=component, value=value, text_value=text_value) | ||||
|  | ||||
| class PackageParameterCreateForm(ComponentParameterCreateForm): | ||||
|     model = parts_models.PackageParameter | ||||
|  | ||||
|     def save(self, package): | ||||
|         param_type = self.cleaned_data['parameter_type'] | ||||
|         if param_type.parameter_type == 'F': | ||||
|             text_value = self.cleaned_data['value'] | ||||
|             value = 0 | ||||
|         else: | ||||
|             text_value = '' | ||||
|             value = self.cleaned_data['number_value'] | ||||
|         self.model.objects.create(parameter_type=param_type, package=package, value=value, text_value=text_value) | ||||
|  | ||||
| class QrSearchForm(forms.Form): | ||||
|     my_qr_validator = QrCodeValidator() | ||||
|   | ||||
| @@ -0,0 +1,37 @@ | ||||
| from django.core.management.base import BaseCommand, CommandParser | ||||
| from django.contrib.auth import get_user_model | ||||
| from parts.models import Component, ComponentParameter, ComponentParameterType, PackageParameter, Package | ||||
|  | ||||
| class Command(BaseCommand): | ||||
| 	help = "Remove component parameters, that are also set on the package with the same value" | ||||
|  | ||||
| 	def add_arguments(self, parser: CommandParser): | ||||
| 		parser.add_argument('--dry-run', | ||||
| 			help='Do not perform parameter deletion. Print only', | ||||
| 			action='store_true') | ||||
|  | ||||
| 	def handle(self, *args, **options): | ||||
| 		# Get all components with set packages. Ignore the ones without packages | ||||
| 		all_comps = Component.objects.exclude(package__isnull=True) | ||||
|  | ||||
| 		for component in all_comps: | ||||
| 			package_parameters = PackageParameter.objects.filter(package=component.package) | ||||
| 			component_parameters = ComponentParameter.objects.filter(component=component) | ||||
| 			package_param_ids = package_parameters.values_list('parameter_type_id', flat=True) | ||||
| 			component_param_ids = component_parameters.values_list('parameter_type_id', flat=True) | ||||
| 			 | ||||
| 			self.stdout.write(f'Comp: {str(component)} Found {len(component_param_ids)} different parameters') | ||||
| 			self.stdout.write(f'\tPackage: {str(component.package)} Found {len(package_param_ids)} different parameters') | ||||
|  | ||||
| 			commontypes = ctypes = ComponentParameterType.objects.filter(id__in=component_param_ids).filter(id__in=package_param_ids) | ||||
| 			self.stdout.write(f'\tCommon parameter count: {len(commontypes)}') | ||||
| 			 | ||||
| 			# Check if values are the same when rendered as a string. This avoids float comparison problems | ||||
| 			for common_type in commontypes: | ||||
| 				s1 = package_parameters.filter(parameter_type=common_type).first().resolved_value_as_string() | ||||
| 				comp_param = component_parameters.filter(parameter_type=common_type).first() | ||||
| 				s2 = comp_param.resolved_value_as_string() | ||||
| 				if s1 == s2: | ||||
| 					self.stdout.write(f'\tParameter {common_type.parameter_name} is the same value for component and package: {s1}. Removing from component') | ||||
| 					if not options['dry_run']: | ||||
| 						comp_param.delete() | ||||
| @@ -0,0 +1,23 @@ | ||||
| from django.core.management.base import BaseCommand, CommandParser | ||||
| from django.contrib.auth import get_user_model | ||||
|  | ||||
| class Command(BaseCommand): | ||||
| 	help = "Create a default superuser if no superuser is already present. This aids automatic deployment inside a container." | ||||
|  | ||||
| 	def add_arguments(self, parser: CommandParser): | ||||
| 		parser.add_argument('--user', | ||||
| 			help='Username to create if no admin account is present', | ||||
| 			default='admin') | ||||
| 		parser.add_argument('--password', | ||||
| 			help='Password to set for newly created user. Ignored, if any admin user is already present', | ||||
| 			default='admin') | ||||
|  | ||||
| 	def handle(self, *args, **options): | ||||
| 		User = get_user_model() | ||||
|  | ||||
| 		# Query if there is any admin user | ||||
| 		if not User.objects.filter(is_superuser=True).exists(): | ||||
| 			self.stdout.write(f'No superuser present. Creating {options['user']} with supplied password') | ||||
| 			User.objects.create_superuser(username=options['user'], password=options['password']) | ||||
| 		else: | ||||
| 			self.stdout.write('At least one superuser already exists. Skipping superuser creation') | ||||
| @@ -8,7 +8,7 @@ from django.db.models import Q | ||||
| from django.forms import formset_factory | ||||
| from django.db import IntegrityError | ||||
| from django.db.models import ProtectedError | ||||
| from ..models import Stock, Component, ComponentParameter, DistributorNum | ||||
| from ..models import Stock, Component, ComponentParameter, DistributorNum, PackageParameter | ||||
| from ..forms import * | ||||
| from .component_import import import_components_from_csv | ||||
| from .generic_views import BaseTemplateMixin | ||||
| @@ -174,6 +174,8 @@ class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|             'distributor__name') | ||||
|         context['parameters'] = ComponentParameter.objects.filter(component=self.object).order_by( | ||||
|             'parameter_type__parameter_name') | ||||
|         context['package_parameters'] = PackageParameter.objects.filter(package=self.object.package).order_by( | ||||
|             'parameter_type__parameter_name') | ||||
|  | ||||
|         return context | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ from django.core.paginator import Paginator | ||||
| from django.db.models import ProtectedError | ||||
| from django.db.models import Q | ||||
| from ..forms import * | ||||
| from ..models import Package | ||||
| from ..models import Package, PackageParameter | ||||
| from .generic_views import BaseTemplateMixin | ||||
|  | ||||
| class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): | ||||
| @@ -82,6 +82,9 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|         context['package'] = self.object | ||||
|         context['edit_form'] = PackageForm(instance=self.object) | ||||
|         context['new_param_form'] = PackageParameterCreateForm() | ||||
|         context['parameters'] = PackageParameter.objects.filter(package=self.object).order_by( | ||||
|             'parameter_type__parameter_name') | ||||
|  | ||||
|         return context | ||||
|  | ||||
| @@ -116,6 +119,27 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|         if not edit_form.is_valid(): | ||||
|             context['edit_form'] = edit_form | ||||
|         return self.render_to_response(context) | ||||
|      | ||||
|     def handle_submit_delete_param_post(self, request, **kwargs): | ||||
|         form = PackageParameterDeleteForm(data=request.POST) | ||||
|         if form.is_valid(): | ||||
|             form.cleaned_data['param_num'].delete() | ||||
|  | ||||
|         context = self.get_context_data(**kwargs) | ||||
|         return self.render_to_response(context) | ||||
|  | ||||
|     def handle_submit_new_param_post(self, request, **kwargs): | ||||
|         form = PackageParameterCreateForm(data=request.POST) | ||||
|  | ||||
|         if form.is_valid(): | ||||
|             try: | ||||
|                 form.save(self.object) | ||||
|             except IntegrityError: | ||||
|                 form.add_error('__all__', 'This parameter is already set') | ||||
|         context = self.get_context_data(**kwargs) | ||||
|         if not form.is_valid(): | ||||
|             context['new_param_form'] = form | ||||
|         return self.render_to_response(context) | ||||
|  | ||||
|     def post(self, request, *args, **kwargs): | ||||
|         self.object = self.get_object() | ||||
| @@ -124,5 +148,9 @@ class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): | ||||
|             return self.handle_delete_package(request) | ||||
|         elif 'submit-pkg-edit' in request.POST: | ||||
|             return self.edit_package(request) | ||||
|         elif 'submit-delete-param' in request.POST: | ||||
|             return self.handle_submit_delete_param_post(request, **kwargs) | ||||
|         elif 'submit-create-new-param' in request.POST: | ||||
|             return self.handle_submit_new_param_post(request, **kwargs) | ||||
|  | ||||
|         return super().post(request, *args, **kwargs) | ||||
| @@ -56,7 +56,6 @@ if get_env_value('DJANGO_FORCE_DEV_MODE', default=False) == 'True': | ||||
|  | ||||
| ALLOWED_HOSTS = ['127.0.0.1', 'localhost', get_env_value('DJANGO_ALLOWED_HOST')] | ||||
|  | ||||
|  | ||||
| # Application definition | ||||
|  | ||||
| INSTALLED_APPS = [ | ||||
| @@ -239,4 +238,7 @@ CSRF_COOKIE_SECURE = True | ||||
|  | ||||
| SECURE_SSL_REDIRECT = False | ||||
|  | ||||
| # allow detection of https behind "old" nginx | ||||
| SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") | ||||
|  | ||||
| SECURE_HSTS_SECONDS = get_env_value('DJANGO_SECURE_HSTS_SECONDS', default=120) | ||||
|   | ||||
| @@ -75,7 +75,7 @@ | ||||
|             'component-parameter-type-list': '{% url 'componentparametertype-list' %}', | ||||
|         }; | ||||
|         </script> | ||||
|         <script type="text/javascript" src="{% static 'js/kenyusho-api-v1.js' %}"></script> | ||||
|         <script type="text/javascript" src="{% static 'js/kenkyusho-api-v1.js' %}"></script> | ||||
|         <script type="text/javascript" src="{% static 'js/autocomplete.js' %}"></script> | ||||
|         <script type="text/javascript" src="{% static 'js/autocomplete-foreign-key-field.js' %}"></script> | ||||
|         <!-- Initialize bootstrap popovers --> | ||||
| @@ -95,4 +95,4 @@ | ||||
|         {% endblock custom_scripts %} | ||||
|  | ||||
|     </body> | ||||
| </html> | ||||
| </html> | ||||
|   | ||||
| @@ -119,6 +119,19 @@ | ||||
|                             <th scope="col"></th> | ||||
|                         </thead> | ||||
|                         <tbody> | ||||
|                             {% for param in package_parameters %} | ||||
|                             <tr> | ||||
|                                 <td> | ||||
|                                     <h6 {% if param.parameter_type.parameter_description %} class="accordion-header" data-bs-toggle="collapse" data-bs-target="#collapse-pkg-parameter-desc-{{forloop.counter}}"{% endif %}> | ||||
|                                         {{param.parameter_type.parameter_name}} | ||||
|                                     </h6> | ||||
|                                 </td> | ||||
|                                 <td> | ||||
|                                     {{param.resolved_value_as_string}} | ||||
|                                 </td> | ||||
|                                 <td><span class="text-info">from Package</span></td> | ||||
|                             </tr> | ||||
|                             {% endfor %} | ||||
|                             {% for param in parameters %} | ||||
|                             <tr> | ||||
|                                 <td> | ||||
| @@ -148,6 +161,13 @@ | ||||
|                         </div> | ||||
|                         {% endif %} | ||||
|                         {% endfor %} | ||||
|                         {% for param in package_parameters %} | ||||
|                         {% if param.parameter_type.parameter_description %} | ||||
|                         <div class="collapse accordion-collapse" id="collapse-pkg-parameter-desc-{{forloop.counter}}" data-bs-parent="#accordion-param-desc"> | ||||
|                             {{param.parameter_type.parameter_description}} | ||||
|                         </div> | ||||
|                         {% endif %} | ||||
|                         {% endfor %} | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="col"> | ||||
|   | ||||
| @@ -26,6 +26,46 @@ | ||||
|             <input type="submit" class="btn btn-primary" value="Save" name="submit-pkg-edit"> | ||||
|         </form> | ||||
|         </div> | ||||
|         <div class="col-md-3"> | ||||
|             <h3>Parameters <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#new-component-parameter-modal"><i class="bi bi-plus-circle"></i></button></h3> | ||||
|             <table class="table align-middle mb-3"> | ||||
|                 <thead> | ||||
|                     <th scope="col">Parameter</th> | ||||
|                     <th scope="col">Value</th> | ||||
|                     <th scope="col"></th> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                     {% for param in parameters %} | ||||
|                     <tr> | ||||
|                         <td> | ||||
|                             <h6 {% if param.parameter_type.parameter_description %} class="accordion-header" data-bs-toggle="collapse" data-bs-target="#collapse-parameter-desc-{{forloop.counter}}"{% endif %}> | ||||
|                                 {{param.parameter_type.parameter_name}} | ||||
|                             </h6> | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             {{param.resolved_value_as_string}} | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             <form method="post"> | ||||
|                                 {% csrf_token %} | ||||
|                                 <input type="hidden" value="{{param.id}}" name="param_num"> | ||||
|                                 <button class="btn btn-danger" name="submit-delete-param">X</button> | ||||
|                             </form>     | ||||
|                         </td> | ||||
|                     </tr> | ||||
|                     {% endfor %} | ||||
|                 </tbody> | ||||
|             </table> | ||||
|             <div class="accordion" id="accordion-param-desc"> | ||||
|                 {% for param in parameters %} | ||||
|                 {% if param.parameter_type.parameter_description %} | ||||
|                 <div class="collapse accordion-collapse" id="collapse-parameter-desc-{{forloop.counter}}" data-bs-parent="#accordion-param-desc"> | ||||
|                     {{param.parameter_type.parameter_description}} | ||||
|                 </div> | ||||
|                 {% endif %} | ||||
|                 {% endfor %} | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| @@ -80,7 +120,7 @@ | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| {% include 'parts/modals/new-component-parameter-modal.html' with component_name=object.name form=new_param_form %} | ||||
|  | ||||
| {% endblock content %} | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| # Startup the db container | ||||
| docker-compose start shimatta-kenkyusho-db | ||||
| docker compose start shimatta-kenkyusho-db | ||||
|  | ||||
| # Override entrypoint to get interactive shell | ||||
| docker-compose run --entrypoint="/bin/sh" -p 8000:8000 shimatta-kenkyusho-web | ||||
| docker compose run --entrypoint="/bin/sh" -p 8000:8000 shimatta-kenkyusho-web | ||||
|   | ||||
		Reference in New Issue
	
	Block a user