Browse Source

初始化

flower_bs 1 month ago
commit
b146202581
100 changed files with 7799 additions and 0 deletions
  1. 1 0
      .gitattributes
  2. 14 0
      .gitignore
  3. 42 0
      Dockerfile
  4. 201 0
      LICENSE
  5. 4 0
      backend_start.sh
  6. 0 0
      company/__init__.py
  7. 4 0
      company/admin.py
  8. 4 0
      company/apps.py
  9. 18 0
      company/filter.py
  10. 0 0
      company/migrations/__init__.py
  11. 19 0
      company/models.py
  12. 54 0
      company/serializers.py
  13. 0 0
      company/tests.py
  14. 12 0
      company/urls.py
  15. 114 0
      company/views.py
  16. 0 0
      customer/__init__.py
  17. 4 0
      customer/admin.py
  18. 5 0
      customer/apps.py
  19. 50 0
      customer/files.py
  20. 20 0
      customer/filter.py
  21. 0 0
      customer/migrations/__init__.py
  22. 20 0
      customer/models.py
  23. 74 0
      customer/serializers.py
  24. 0 0
      customer/tests.py
  25. 13 0
      customer/urls.py
  26. 169 0
      customer/views.py
  27. 0 0
      cyclecount/__init__.py
  28. 4 0
      cyclecount/admin.py
  29. 4 0
      cyclecount/apps.py
  30. 52 0
      cyclecount/files.py
  31. 38 0
      cyclecount/filter.py
  32. 0 0
      cyclecount/migrations/__init__.py
  33. 57 0
      cyclecount/models.py
  34. 9 0
      cyclecount/page.py
  35. 120 0
      cyclecount/serializers.py
  36. 0 0
      cyclecount/tests.py
  37. 15 0
      cyclecount/urls.py
  38. 532 0
      cyclecount/views.py
  39. 0 0
      dashboard/__init__.py
  40. 3 0
      dashboard/admin.py
  41. 5 0
      dashboard/apps.py
  42. 0 0
      dashboard/migrations/__init__.py
  43. 3 0
      dashboard/models.py
  44. 3 0
      dashboard/tests.py
  45. 7 0
      dashboard/urls.py
  46. 202 0
      dashboard/views.py
  47. 40 0
      docker-compose.yml
  48. 67 0
      greaterwms/__init__.py
  49. 19 0
      greaterwms/asgi.py
  50. 356 0
      greaterwms/settings.py
  51. 63 0
      greaterwms/urls.py
  52. 56 0
      greaterwms/views.py
  53. 16 0
      greaterwms/wsgi.py
  54. 22 0
      manage.py
  55. 0 0
      media/__init__.py
  56. 97 0
      nginx.conf
  57. 52 0
      requirements.txt
  58. 0 0
      static/__init__.py
  59. BIN
      static/img/GreaterWMS.png
  60. BIN
      static/img/GreaterWMS_en.png
  61. BIN
      static/img/Katie.jpg
  62. BIN
      static/img/alipay.jpg
  63. BIN
      static/img/contact.png
  64. BIN
      static/img/dongtai.png
  65. BIN
      static/img/dongtai1.png
  66. BIN
      static/img/github.png
  67. BIN
      static/img/logo.png
  68. BIN
      static/img/logout.png
  69. BIN
      static/img/mobile_dn.jpg
  70. BIN
      static/img/mobile_dn_en.jpg
  71. BIN
      static/img/mobile_equ.jpg
  72. BIN
      static/img/mobile_equ_en.jpg
  73. BIN
      static/img/mobile_splash.jpg
  74. BIN
      static/img/money.png
  75. BIN
      static/img/photo.png
  76. BIN
      static/img/profile.png
  77. BIN
      static/img/register.png
  78. BIN
      static/img/user.jpg
  79. BIN
      static/img/user.png
  80. BIN
      static/img/video.png
  81. BIN
      static/img/wechat.jpg
  82. 0 0
      static_new/__init__.py
  83. 275 0
      static_new/admin/css/autocomplete.css
  84. 1089 0
      static_new/admin/css/base.css
  85. 325 0
      static_new/admin/css/changelists.css
  86. 33 0
      static_new/admin/css/dark_mode.css
  87. 26 0
      static_new/admin/css/dashboard.css
  88. 20 0
      static_new/admin/css/fonts.css
  89. 528 0
      static_new/admin/css/forms.css
  90. 61 0
      static_new/admin/css/login.css
  91. 139 0
      static_new/admin/css/nav_sidebar.css
  92. 1015 0
      static_new/admin/css/responsive.css
  93. 80 0
      static_new/admin/css/responsive_rtl.css
  94. 239 0
      static_new/admin/css/rtl.css
  95. 21 0
      static_new/admin/css/vendor/select2/LICENSE-SELECT2.md
  96. 481 0
      static_new/admin/css/vendor/select2/select2.css
  97. 1 0
      static_new/admin/css/vendor/select2/select2.min.css
  98. 580 0
      static_new/admin/css/widgets.css
  99. 202 0
      static_new/admin/fonts/LICENSE.txt
  100. 0 0
      static_new/admin/fonts/README.txt

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+*.vue linguist-language=python

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+node_modules/
+__pycache__/
+Authorization/
+authorization.txt
+authorization.json
+db.sqlite3
+.idea/
+delete.py
+*.xlsx
+logs/
+bfg.jar
+error.log
+server.log
+/.history/

+ 42 - 0
Dockerfile

@@ -0,0 +1,42 @@
+FROM --platform=linux/amd64 python:3.8.10-slim AS backend
+RUN mkdir -p /GreaterWMS/templates
+#copy requirements.txt
+ADD ./requirements.txt /GreaterWMS/requirements.txt
+COPY ./backend_start.sh /GreaterWMS/backend_start.sh
+#Configure working directory
+WORKDIR /GreaterWMS
+ENV port = ${port}
+#Installation foundation dependency
+#RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
+#RUN apt-get clean
+RUN apt-get update --fix-missing && apt-get upgrade -y
+RUN apt-get install build-essential -y
+RUN apt-get install supervisor -y
+#Configure pip3 Alibaba Source
+#RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple/
+#RUN pip3 config set install.trusted-host mirrors.aliyun.com
+RUN python3 -m pip install --upgrade pip
+#Install supervisor daphne
+RUN pip3 install supervisor
+RUN pip3 install -U 'Twisted[tls,http2]'
+RUN pip3 install -r requirements.txt
+RUN pip3 install daphne
+RUN chmod +x /GreaterWMS/backend_start.sh
+CMD ["/GreaterWMS/backend_start.sh"]
+
+FROM --platform=linux/amd64 node:14.19.3-buster-slim AS front
+COPY ./templates/package.json /GreaterWMS/templates/package.json
+#COPY ./templates/node_modules/ /GreaterWMS/templates/node_modules/
+COPY ./web_start.sh /GreaterWMS/templates/web_start.sh
+ENV port = ${port}
+#ENV NODE_OPTIONS=--openssl-legacy-provider
+RUN cd  /GreaterWMS/templates
+RUN npm install -g npm --force
+#RUN npm config set registry https://registry.npm.taobao.org
+RUN npm install -g yarn --force
+#RUN yarn config set registry https://registry.npm.taobao.org
+RUN npm install -g @quasar/cli --force
+RUN yarn install
+RUN chmod +x /GreaterWMS/templates/web_start.sh
+ENTRYPOINT ["/GreaterWMS/templates/web_start.sh"]
+

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 4 - 0
backend_start.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+python3 manage.py makemigrations
+python3 manage.py migrate
+supervisord -c /etc/supervisor/supervisord.conf

+ 0 - 0
company/__init__.py


+ 4 - 0
company/admin.py

@@ -0,0 +1,4 @@
+from django.contrib import admin
+from .models import ListModel
+
+admin.site.register(ListModel)

+ 4 - 0
company/apps.py

@@ -0,0 +1,4 @@
+from django.apps import AppConfig
+
+class CompanyConfig(AppConfig):
+    name = 'company'

+ 18 - 0
company/filter.py

@@ -0,0 +1,18 @@
+from django_filters import FilterSet
+from .models import ListModel
+
+class Filter(FilterSet):
+    class Meta:
+        model = ListModel
+        fields = {
+            "id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "company_name": ['exact', 'iexact', 'contains', 'icontains'],
+            "company_city": ['exact', 'iexact', 'contains', 'icontains'],
+            "company_address": ['exact', 'iexact', 'contains', 'icontains'],
+            "company_contact": ['exact', 'iexact', 'contains', 'icontains'],
+            "company_manager": ['exact', 'iexact', 'contains', 'icontains'],
+            "creater": ['exact', 'iexact', 'contains', 'icontains'],
+            "is_delete": ['exact', 'iexact'],
+            "create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
+            "update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
+        }

+ 0 - 0
company/migrations/__init__.py


+ 19 - 0
company/models.py

@@ -0,0 +1,19 @@
+from django.db import models
+
+class ListModel(models.Model):
+    company_name = models.CharField(max_length=255, verbose_name="Company Name")
+    company_city = models.CharField(max_length=255, verbose_name="Company City")
+    company_address = models.CharField(max_length=255, verbose_name="Company Address")
+    company_contact = models.CharField(max_length=255, verbose_name="Company Contact")
+    company_manager = models.CharField(max_length=255, verbose_name="Company Manager")
+    creater = models.CharField(max_length=255, verbose_name="Who Created")
+    openid = models.CharField(max_length=255, verbose_name="Openid")
+    is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
+    update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
+
+    class Meta:
+        db_table = 'company'
+        verbose_name = 'Company'
+        verbose_name_plural = "Company"
+        ordering = ['company_name']

+ 54 - 0
company/serializers.py

@@ -0,0 +1,54 @@
+from rest_framework import serializers
+from .models import ListModel
+from utils import datasolve
+
+class CompanyGetSerializer(serializers.ModelSerializer):
+    company_name = serializers.CharField(read_only=True, required=False)
+    company_city = serializers.CharField(read_only=True, required=False)
+    company_address = serializers.CharField(read_only=True, required=False)
+    company_contact = serializers.CharField(read_only=True, required=False)
+    company_manager = serializers.CharField(read_only=True, required=False)
+    creater = serializers.CharField(read_only=True, required=False)
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    class Meta:
+        model = ListModel
+        exclude = ['openid', 'is_delete', ]
+        read_only_fields = ['id']
+
+class CompanyPostSerializer(serializers.ModelSerializer):
+    openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
+    company_name = serializers.CharField(read_only=False,  required=True, validators=[datasolve.data_validate])
+    company_city = serializers.CharField(read_only=False,  required=True, validators=[datasolve.data_validate])
+    company_address = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    company_contact = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    company_manager = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    class Meta:
+        model = ListModel
+        exclude = ['is_delete', ]
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+class CompanyUpdateSerializer(serializers.ModelSerializer):
+    company_name = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    company_city = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    company_address = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    company_contact = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    company_manager = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    class Meta:
+        model = ListModel
+        exclude = ['openid', 'is_delete', ]
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+class CompanyPartialUpdateSerializer(serializers.ModelSerializer):
+    company_name = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    company_city = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    company_address = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    company_contact = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    company_manager = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    creater = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    class Meta:
+        model = ListModel
+        exclude = ['openid', 'is_delete', ]
+        read_only_fields = ['id', 'create_time', 'update_time', ]

+ 0 - 0
company/tests.py


+ 12 - 0
company/urls.py

@@ -0,0 +1,12 @@
+from django.urls import path, re_path
+from . import views
+
+urlpatterns = [
+path(r'', views.APIViewSet.as_view({"get": "list", "post": "create"}), name="company"),
+re_path(r'^(?P<pk>\d+)/$', views.APIViewSet.as_view({
+    'get': 'retrieve',
+    'put': 'update',
+    'patch': 'partial_update',
+    'delete': 'destroy'
+}), name="company_1")
+]

+ 114 - 0
company/views.py

@@ -0,0 +1,114 @@
+from rest_framework import viewsets
+from .models import ListModel
+from . import serializers
+from utils.page import MyPageNumberPagination
+from rest_framework.filters import OrderingFilter
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework.response import Response
+from .filter import Filter
+from rest_framework.exceptions import APIException
+
+class APIViewSet(viewsets.ModelViewSet):
+    """
+        retrieve:
+            Response a data list(get)
+
+        list:
+            Response a data list(all)
+
+        create:
+            Create a data line(post)
+
+        delete:
+            Delete a data line(delete)
+
+        partial_update:
+            Partial_update a data(patch:partial_update)
+
+        update:
+            Update a data(put:update)
+    """
+    pagination_class = MyPageNumberPagination
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = Filter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
+            else:
+                return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
+        else:
+            return ListModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list', 'retrieve', 'destroy']:
+            return serializers.CompanyGetSerializer
+        elif self.action in ['create']:
+            return serializers.CompanyPostSerializer
+        elif self.action in ['update']:
+            return serializers.CompanyUpdateSerializer
+        elif self.action in ['partial_update']:
+            return serializers.CompanyPartialUpdateSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def create(self, request, *args, **kwargs):
+        data = self.request.data
+        data['openid'] = self.request.auth.openid
+        if ListModel.objects.filter(openid=data['openid'], company_name=data['company_name'], is_delete=False).exists():
+            raise APIException({"detail": "Data exists"})
+        else:
+            if ListModel.objects.filter(openid=data['openid'], is_delete=False).count() >= 1:
+                raise APIException({"detail": "You Just Can Create 1 Company"})
+            else:
+                serializer = self.get_serializer(data=data)
+                serializer.is_valid(raise_exception=True)
+                serializer.save()
+                headers = self.get_success_headers(serializer.data)
+                return Response(serializer.data, status=200, headers=headers)
+
+    def update(self, request, pk):
+        qs = self.get_object()
+        if qs.openid != self.request.auth.openid:
+            raise APIException({"detail": "Cannot update data which not yours"})
+        else:
+            data = self.request.data
+            serializer = self.get_serializer(qs, data=data)
+            serializer.is_valid(raise_exception=True)
+            serializer.save()
+            headers = self.get_success_headers(serializer.data)
+            return Response(serializer.data, status=200, headers=headers)
+
+    def partial_update(self, request, pk):
+        qs = self.get_object()
+        if qs.openid != self.request.auth.openid:
+            raise APIException({"detail": "Cannot partial_update data which not yours"})
+        else:
+            data = self.request.data
+            serializer = self.get_serializer(qs, data=data, partial=True)
+            serializer.is_valid(raise_exception=True)
+            serializer.save()
+            headers = self.get_success_headers(serializer.data)
+            return Response(serializer.data, status=200, headers=headers)
+
+    def destroy(self, request, pk):
+        qs = self.get_object()
+        if qs.openid != self.request.auth.openid:
+            raise APIException({"detail": "Cannot delete data which not yours"})
+        else:
+            qs.is_delete = True
+            qs.save()
+            serializer = self.get_serializer(qs, many=False)
+            headers = self.get_success_headers(serializer.data)
+            return Response(serializer.data, status=200, headers=headers)
+

+ 0 - 0
customer/__init__.py


+ 4 - 0
customer/admin.py

@@ -0,0 +1,4 @@
+from django.contrib import admin
+from .models import ListModel
+
+admin.site.register(ListModel)

+ 5 - 0
customer/apps.py

@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class CustomerConfig(AppConfig):
+    name = 'customer'

+ 50 - 0
customer/files.py

@@ -0,0 +1,50 @@
+from rest_framework_csv.renderers import CSVStreamingRenderer
+
+
+def file_headers():
+    return [
+        'customer_name',
+        'customer_city',
+        'customer_address',
+        'customer_contact',
+        'customer_manager',
+        'customer_level',
+        'creater',
+        'create_time',
+        'update_time'
+    ]
+
+def cn_data_header():
+    return dict([
+        ('customer_name', u'客户名称'),
+        ('customer_city', u'客户城市'),
+        ('customer_address', u'详细地址'),
+        ('customer_contact', u'联系电话'),
+        ('customer_manager', u'负责人'),
+        ('customer_level', u'客户等级'),
+        ('creater', u'创建人'),
+        ('create_time', u'创建时间'),
+        ('update_time', u'更新时间'),
+    ])
+
+def en_data_header():
+    return dict([
+        ('customer_name', u'Customer Name'),
+        ('customer_city', u'Customer City'),
+        ('customer_address', u'Customer Address'),
+        ('customer_contact', u'Customer Contact'),
+        ('customer_manager', u'Customer Manager'),
+        ('customer_level', u'Customer Level'),
+        ('creater', u'Creater'),
+        ('create_time', u'Create Time'),
+        ('update_time', u'Update Time'),
+    ])
+
+
+class FileRenderCN(CSVStreamingRenderer):
+    header = file_headers()
+    labels = cn_data_header()
+
+class FileRenderEN(CSVStreamingRenderer):
+    header = file_headers()
+    labels = en_data_header()

+ 20 - 0
customer/filter.py

@@ -0,0 +1,20 @@
+from django_filters import FilterSet
+from .models import ListModel
+
+class Filter(FilterSet):
+    class Meta:
+        model = ListModel
+        fields = {
+            "id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "customer_name": ['exact', 'iexact', 'contains', 'icontains'],
+            "customer_city": ['exact', 'iexact', 'contains', 'icontains'],
+            "customer_address": ['exact', 'iexact', 'contains', 'icontains'],
+            "customer_contact": ['exact', 'iexact', 'contains', 'icontains'],
+            "customer_manager": ['exact', 'iexact', 'contains', 'icontains'],
+            "customer_level": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "creater": ['exact', 'iexact', 'contains', 'icontains'],
+            "is_delete": ['exact', 'iexact'],
+            "create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
+            "update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
+        }
+

+ 0 - 0
customer/migrations/__init__.py


+ 20 - 0
customer/models.py

@@ -0,0 +1,20 @@
+from django.db import models
+
+class ListModel(models.Model):
+    customer_name = models.CharField(max_length=255, verbose_name="Customer Name")
+    customer_city = models.CharField(max_length=255, verbose_name="Customer City")
+    customer_address = models.CharField(max_length=255, verbose_name="Customer Address")
+    customer_contact = models.CharField(max_length=255, verbose_name="Customer Contact")
+    customer_manager = models.CharField(max_length=255, verbose_name="Customer Manager")
+    customer_level = models.BigIntegerField(default=1, verbose_name="Customer Level")
+    creater = models.CharField(max_length=255, verbose_name="Who Created")
+    openid = models.CharField(max_length=255, verbose_name="Openid")
+    is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
+    update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
+
+    class Meta:
+        db_table = 'customer'
+        verbose_name = 'Customer'
+        verbose_name_plural = "Customer"
+        ordering = ['customer_name']

+ 74 - 0
customer/serializers.py

@@ -0,0 +1,74 @@
+from rest_framework import serializers
+from .models import ListModel
+from utils import datasolve
+
+class CustomerGetSerializer(serializers.ModelSerializer):
+    customer_name = serializers.CharField(read_only=True, required=False)
+    customer_city = serializers.CharField(read_only=True, required=False)
+    customer_address = serializers.CharField(read_only=True, required=False)
+    customer_contact = serializers.CharField(read_only=True, required=False)
+    customer_manager = serializers.CharField(read_only=True, required=False)
+    customer_level = serializers.IntegerField(read_only=True, required=False)
+    creater = serializers.CharField(read_only=True, required=False)
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    class Meta:
+        model = ListModel
+        exclude = ['openid', 'is_delete', ]
+        read_only_fields = ['id', ]
+
+class CustomerPostSerializer(serializers.ModelSerializer):
+    openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
+    customer_name = serializers.CharField(read_only=False,  required=True, validators=[datasolve.data_validate])
+    customer_city = serializers.CharField(read_only=False,  required=True, validators=[datasolve.data_validate])
+    customer_address = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    customer_contact = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    customer_manager = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    customer_level = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.data_validate])
+    creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    class Meta:
+        model = ListModel
+        exclude = ['is_delete', ]
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+class CustomerUpdateSerializer(serializers.ModelSerializer):
+    customer_name = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    customer_city = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    customer_address = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    customer_contact = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    customer_manager = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    customer_level = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.data_validate])
+    creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    class Meta:
+        model = ListModel
+        exclude = ['openid', 'is_delete', ]
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+class CustomerPartialUpdateSerializer(serializers.ModelSerializer):
+    customer_name = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    customer_city = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    customer_address = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    customer_contact = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    customer_manager = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    customer_level = serializers.IntegerField(read_only=False, required=False, validators=[datasolve.data_validate])
+    creater = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
+    class Meta:
+        model = ListModel
+        exclude = ['openid', 'is_delete', ]
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+class FileRenderSerializer(serializers.ModelSerializer):
+    customer_name = serializers.CharField(read_only=False, required=False)
+    customer_city = serializers.CharField(read_only=False, required=False)
+    customer_address = serializers.CharField(read_only=False, required=False)
+    customer_contact = serializers.CharField(read_only=False, required=False)
+    customer_manager = serializers.CharField(read_only=False, required=False)
+    customer_level = serializers.IntegerField(read_only=False, required=False)
+    creater = serializers.CharField(read_only=False, required=False)
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+
+    class Meta:
+        model = ListModel
+        ref_name = 'CustomereFileRenderSerializer'
+        exclude = ['openid', 'is_delete', ]

+ 0 - 0
customer/tests.py


+ 13 - 0
customer/urls.py

@@ -0,0 +1,13 @@
+from django.urls import path, re_path
+from . import views
+
+urlpatterns = [
+path(r'', views.APIViewSet.as_view({"get": "list", "post": "create"}), name="customer"),
+path(r'file/', views.FileDownloadView.as_view({"get": "list"}), name="customerfiledownload"),
+re_path(r'^(?P<pk>\d+)/$', views.APIViewSet.as_view({
+    'get': 'retrieve',
+    'put': 'update',
+    'patch': 'partial_update',
+    'delete': 'destroy'
+}), name="customer_1")
+]

+ 169 - 0
customer/views.py

@@ -0,0 +1,169 @@
+from django.http import StreamingHttpResponse
+from rest_framework import viewsets
+from rest_framework.settings import api_settings
+from .files import FileRenderCN, FileRenderEN
+from .models import ListModel
+from . import serializers
+from utils.page import MyPageNumberPagination
+from rest_framework.filters import OrderingFilter
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework.response import Response
+from .filter import Filter
+from rest_framework.exceptions import APIException
+from .serializers import FileRenderSerializer
+
+class APIViewSet(viewsets.ModelViewSet):
+    """
+        retrieve:
+            Response a data list(get)
+
+        list:
+            Response a data list(all)
+
+        create:
+            Create a data line(post)
+
+        delete:
+            Delete a data line(delete)
+
+        partial_update:
+            Partial_update a data(patch:partial_update)
+
+        update:
+            Update a data(put:update)
+    """
+    pagination_class = MyPageNumberPagination
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = Filter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
+            else:
+                return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
+        else:
+            return ListModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list', 'retrieve', 'destroy']:
+            return serializers.CustomerGetSerializer
+        elif self.action in ['create']:
+            return serializers.CustomerPostSerializer
+        elif self.action in ['update']:
+            return serializers.CustomerUpdateSerializer
+        elif self.action in ['partial_update']:
+            return serializers.CustomerPartialUpdateSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def create(self, request, *args, **kwargs):
+        data = self.request.data
+        data['openid'] = self.request.auth.openid
+        if ListModel.objects.filter(openid=data['openid'], customer_name=data['customer_name'], is_delete=False).exists():
+            raise APIException({"detail": "Data exists"})
+        else:
+            serializer = self.get_serializer(data=data)
+            serializer.is_valid(raise_exception=True)
+            serializer.save()
+            headers = self.get_success_headers(serializer.data)
+            return Response(serializer.data, status=200, headers=headers)
+
+    def update(self, request, pk):
+        qs = self.get_object()
+        if qs.openid != self.request.auth.openid:
+            raise APIException({"detail": "Cannot update data which not yours"})
+        else:
+            data = self.request.data
+            serializer = self.get_serializer(qs, data=data)
+            serializer.is_valid(raise_exception=True)
+            serializer.save()
+            headers = self.get_success_headers(serializer.data)
+            return Response(serializer.data, status=200, headers=headers)
+
+    def partial_update(self, request, pk):
+        qs = self.get_object()
+        if qs.openid != self.request.auth.openid:
+            raise APIException({"detail": "Cannot partial_update data which not yours"})
+        else:
+            data = self.request.data
+            serializer = self.get_serializer(qs, data=data, partial=True)
+            serializer.is_valid(raise_exception=True)
+            serializer.save()
+            headers = self.get_success_headers(serializer.data)
+            return Response(serializer.data, status=200, headers=headers)
+
+    def destroy(self, request, pk):
+        qs = self.get_object()
+        if qs.openid != self.request.auth.openid:
+            raise APIException({"detail": "Cannot delete data which not yours"})
+        else:
+            qs.is_delete = True
+            qs.save()
+            serializer = self.get_serializer(qs, many=False)
+            headers = self.get_success_headers(serializer.data)
+            return Response(serializer.data, status=200, headers=headers)
+
+
+class FileDownloadView(viewsets.ModelViewSet):
+    renderer_classes = (FileRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = Filter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
+            else:
+                return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
+        else:
+            return ListModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.FileRenderSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def get_lang(self, data):
+        lang = self.request.META.get('HTTP_LANGUAGE')
+        if lang:
+            if lang == 'zh-hans':
+                return FileRenderCN().render(data)
+            else:
+                return FileRenderEN().render(data)
+        else:
+            return FileRenderEN().render(data)
+
+    def list(self, request, *args, **kwargs):
+        from datetime import datetime
+        dt = datetime.now()
+        data = (
+            FileRenderSerializer(instance).data
+            for instance in self.filter_queryset(self.get_queryset())
+        )
+        renderer = self.get_lang(data)
+        response = StreamingHttpResponse(
+            renderer,
+            content_type="text/csv"
+        )
+        response['Content-Disposition'] = "attachment; filename='customer_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
+        return response

+ 0 - 0
cyclecount/__init__.py


+ 4 - 0
cyclecount/admin.py

@@ -0,0 +1,4 @@
+from django.contrib import admin
+from .models import CyclecountModeDayModel
+
+admin.site.register(CyclecountModeDayModel)

+ 4 - 0
cyclecount/apps.py

@@ -0,0 +1,4 @@
+from django.apps import AppConfig
+
+class CyclecountConfig(AppConfig):
+    name = 'cyclecount'

+ 52 - 0
cyclecount/files.py

@@ -0,0 +1,52 @@
+from rest_framework_csv.renderers import CSVStreamingRenderer
+
+def file_headers():
+    return [
+        "cyclecount_status",
+        "bin_name",
+        "goods_code",
+        "goods_desc",
+        "goods_qty",
+        "physical_inventory",
+        "difference",
+        "creater",
+        "create_time",
+        "update_time"
+    ]
+
+def cn_data_header():
+    return dict([
+        ('cyclecount_status', u'盘点状态'),
+        ('bin_name', u'库位名称'),
+        ('goods_code', u'商品编码'),
+        ('goods_desc', u'商品描述'),
+        ('goods_qty', u'现有数量'),
+        ('physical_inventory', u'盘点数量'),
+        ('difference', u'盘点差异'),
+        ('creater', u'创建人'),
+        ('create_time', u'创建时间'),
+        ('update_time', u'盘点时间')
+    ])
+
+def en_data_header():
+    return dict([
+        ('cyclecount_status', u'Count Status'),
+        ('bin_nam', u'Bin Name'),
+        ('goods_code', u'Goods Code'),
+        ('goods_desc', u'Goods Description'),
+        ('goods_qty', u'On-Hand Stock'),
+        ('physical_inventory', u'Count QTY'),
+        ('difference', u'Count Difference'),
+        ('creater', u'Creater'),
+        ('create_time', u'Create Time'),
+        ('update_time', u'Update Time')
+    ])
+
+
+class FileRenderCN(CSVStreamingRenderer):
+    header = file_headers()
+    labels = cn_data_header()
+
+class FileRenderEN(CSVStreamingRenderer):
+    header = file_headers()
+    labels = en_data_header()

+ 38 - 0
cyclecount/filter.py

@@ -0,0 +1,38 @@
+from django_filters import FilterSet
+from .models import CyclecountModeDayModel
+from .models import QTYRecorder
+from .models import ManualCyclecountModeModel
+
+class Filter(FilterSet):
+    class Meta:
+        model = CyclecountModeDayModel
+        fields = {
+            "id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
+        }
+
+class QTYRecorderListFilter(FilterSet):
+    class Meta:
+        model = QTYRecorder
+        fields = {
+            "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "mode_code": ['exact', 'iexact', 'contains', 'icontains'],
+            "bin_name": ['exact', 'iexact', 'contains', 'icontains'],
+            "goods_code": ['exact', 'iexact', 'contains', 'icontains'],
+            "goods_desc": ['exact', 'iexact', 'contains', 'icontains'],
+            "goods_qty": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'range'],
+            "store_code": ['exact', 'iexact', 'contains', 'icontains'],
+            "creater": ['exact', 'iexact', 'contains', 'icontains'],
+            "create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
+            "update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
+        }
+
+class ManualFilter(FilterSet):
+    class Meta:
+        model = ManualCyclecountModeModel
+        fields = {
+            "id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
+            "bin_name": ['exact', 'iexact', 'contains', 'icontains'],
+            "goods_code": ['exact', 'iexact', 'contains', 'icontains'],
+        }

+ 0 - 0
cyclecount/migrations/__init__.py


+ 57 - 0
cyclecount/models.py

@@ -0,0 +1,57 @@
+from django.db import models
+
+class QTYRecorder(models.Model):
+    openid = models.CharField(max_length=255, verbose_name="Openid")
+    mode_code = models.CharField(max_length=255, verbose_name="Transaction Mode")
+    bin_name = models.CharField(max_length=255, verbose_name="Bin Name")
+    goods_code = models.CharField(max_length=255, verbose_name="Goods Code")
+    goods_desc = models.CharField(max_length=255, verbose_name="Goods Description")
+    goods_qty = models.BigIntegerField(default=0, verbose_name="On Hand Stock")
+    store_code = models.CharField(default='', max_length=255, verbose_name="Store Code")
+    creater = models.CharField(max_length=255, verbose_name="Who Create")
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
+    update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
+
+    class Meta:
+        db_table = 'qtyrecorder'
+        verbose_name = 'QTY Recorder'
+        verbose_name_plural = "QTY Recorder"
+        ordering = ['-id']
+
+class CyclecountModeDayModel(models.Model):
+    openid = models.CharField(max_length=255, verbose_name="Openid")
+    cyclecount_status = models.IntegerField(default=0, verbose_name="Cycle Count Status")
+    bin_name = models.CharField(max_length=255, verbose_name="Bin Name")
+    goods_code = models.CharField(max_length=255, verbose_name="Goods Code")
+    goods_qty = models.BigIntegerField(default=0, verbose_name="On Hand Stock")
+    physical_inventory = models.BigIntegerField(default=0, verbose_name="Goods Code")
+    difference = models.BigIntegerField(default=0, verbose_name="Goods Code")
+    creater = models.CharField(max_length=255, verbose_name="Who Create")
+    t_code = models.CharField(max_length=255, verbose_name="Transaction Code")
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
+    update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
+
+    class Meta:
+        db_table = 'cyclecountday'
+        verbose_name = 'Cyclecount Day'
+        verbose_name_plural = "Cyclecount Day"
+        ordering = ['openid']
+
+class ManualCyclecountModeModel(models.Model):
+    openid = models.CharField(max_length=255, verbose_name="Openid")
+    cyclecount_status = models.IntegerField(default=0, verbose_name="Cycle Count Status")
+    bin_name = models.CharField(max_length=255, verbose_name="Bin Name")
+    goods_code = models.CharField(max_length=255, verbose_name="Goods Code")
+    goods_qty = models.BigIntegerField(default=0, verbose_name="On Hand Stock")
+    physical_inventory = models.BigIntegerField(default=0, verbose_name="Goods Code")
+    difference = models.BigIntegerField(default=0, verbose_name="Goods Code")
+    creater = models.CharField(max_length=255, verbose_name="Who Create")
+    t_code = models.CharField(max_length=255, verbose_name="Transaction Code")
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
+    update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
+
+    class Meta:
+        db_table = 'manualcyclecount'
+        verbose_name = 'Manual Cyclecount'
+        verbose_name_plural = "Manual Cyclecount"
+        ordering = ['openid']

+ 9 - 0
cyclecount/page.py

@@ -0,0 +1,9 @@
+from rest_framework.pagination import PageNumberPagination
+from rest_framework.utils.urls import replace_query_param, remove_query_param
+from rest_framework.exceptions import APIException
+
+class CycleCountPageNumberPagination(PageNumberPagination):
+    page_size = 10000
+    page_size_query_param = "max_page"
+    max_page_size = 20000
+    page_query_param = 'page'

+ 120 - 0
cyclecount/serializers.py

@@ -0,0 +1,120 @@
+from rest_framework import serializers
+from .models import CyclecountModeDayModel
+from .models import ManualCyclecountModeModel
+from utils import datasolve
+from .models import QTYRecorder
+class CyclecountGetSerializer(serializers.ModelSerializer):
+    creater = serializers.CharField(read_only=True, required=False)
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    class Meta:
+        model = CyclecountModeDayModel
+        exclude = ['openid']
+        read_only_fields = ['id', ]
+
+class CyclecountPostSerializer(serializers.ModelSerializer):
+    openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
+    creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    class Meta:
+        model = CyclecountModeDayModel
+        exclude = []
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+class CyclecountUpdateSerializer(serializers.ModelSerializer):
+    openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
+    creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    class Meta:
+        model = CyclecountModeDayModel
+        exclude = []
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+class FileRenderSerializer(serializers.ModelSerializer):
+    creater = serializers.CharField(read_only=False, required=False)
+    physical_inventory = serializers.SerializerMethodField()
+    difference = serializers.SerializerMethodField()
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+
+    class Meta:
+        model = CyclecountModeDayModel
+        ref_name = 'CyclecountFileRenderSerializer'
+        exclude = ['openid']
+
+    def get_physical_inventory(self, obj):
+        return ''
+
+    def get_difference(self, obj):
+        return ''
+
+class FileRenderAllSerializer(serializers.ModelSerializer):
+    creater = serializers.CharField(read_only=False, required=False)
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+
+    class Meta:
+        model = CyclecountModeDayModel
+        ref_name = 'CyclecountFileRenderAllSerializer'
+        exclude = ['openid']
+
+
+class QTYRecorderSerializer(serializers.ModelSerializer):
+    mode_code = serializers.CharField(read_only=True, required=False)
+    bin_name = serializers.CharField(read_only=True, required=False)
+    goods_code = serializers.CharField(read_only=True, required=False)
+    goods_desc = serializers.CharField(read_only=True, required=False)
+    goods_qty = serializers.IntegerField(read_only=True, required=False)
+    store_code = serializers.CharField(read_only=True, required=False)
+    creater = serializers.CharField(read_only=True, required=False)
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+
+    class Meta:
+        model = QTYRecorder
+        ref_name = 'QTYRecorderSerializer'
+        exclude = ['openid']
+
+class ManualCyclecountGetSerializer(serializers.ModelSerializer):
+    creater = serializers.CharField(read_only=True, required=False)
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    class Meta:
+        model = ManualCyclecountModeModel
+        exclude = ['openid']
+        read_only_fields = ['id', ]
+
+
+class ManualCyclecountPostSerializer(serializers.ModelSerializer):
+    openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
+    creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    class Meta:
+        model = ManualCyclecountModeModel
+        exclude = []
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+class ManualCyclecountUpdateSerializer(serializers.ModelSerializer):
+    openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
+    creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
+    class Meta:
+        model = ManualCyclecountModeModel
+        exclude = []
+        read_only_fields = ['id', 'create_time', 'update_time', ]
+
+
+class ManualFileRenderSerializer(serializers.ModelSerializer):
+    creater = serializers.CharField(read_only=False, required=False)
+    physical_inventory = serializers.SerializerMethodField()
+    difference = serializers.SerializerMethodField()
+    create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+    update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
+
+    class Meta:
+        model = ManualCyclecountModeModel
+        ref_name = 'ManualFileRenderSerializer'
+        exclude = ['openid']
+
+    def get_physical_inventory(self, obj):
+        return ''
+
+    def get_difference(self, obj):
+        return ''
+

+ 0 - 0
cyclecount/tests.py


+ 15 - 0
cyclecount/urls.py

@@ -0,0 +1,15 @@
+from django.urls import path
+from . import views
+
+urlpatterns = [
+path(r'', views.CyclecountModeDayViewSet.as_view({"get": "list", "post": "create", 'put': 'update'}), name="cyclecount"),
+path(r'cyclecountrecorder/', views.CyclecountModeAllViewSet.as_view({"get": "list"}), name="cyclecountrecorder"),
+path(r'filecyclecountday/', views.FileDownloadView.as_view({"get": "list"}), name="filecyclecountday"),
+path(r'filecyclecountall/', views.FileDownloadAllView.as_view({"get": "list"}), name="filecyclecountall"),
+
+path(r'qtyrecorviewset/', views.QTYRecorderViewSet.as_view({"get": "list"}), name="qtyrecorviewset"),
+path(r'getgoodscyclecount/', views.GetGoodsCyclecountViewSet.as_view({"get": "list"}), name="getgoodscyclecount"),
+path(r'manualcyclecount/', views.ManualCyclecountViewSet.as_view({"post": "create", "get": "list"}), name="manualcyclecount"),
+path(r'manualfilecyclecount/', views.ManualFileDownloadView.as_view({"get": "list"}), name="manualfilecyclecount"),
+path(r'manualcyclecountrecorder/', views.ManualCyclecountRecorderViewSet.as_view({"get": "list"}), name="manualcyclecountrecorder")
+]

+ 532 - 0
cyclecount/views.py

@@ -0,0 +1,532 @@
+import traceback
+from dateutil.relativedelta import relativedelta
+from django.http import StreamingHttpResponse
+from django.utils import timezone
+from rest_framework import viewsets
+from rest_framework.settings import api_settings
+from .files import FileRenderCN, FileRenderEN
+from .models import CyclecountModeDayModel
+from . import serializers
+from utils.page import MyPageNumberPagination
+from rest_framework.filters import OrderingFilter
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework.response import Response
+from .filter import Filter
+from .filter import ManualFilter
+from .filter import QTYRecorderListFilter
+from .serializers import FileRenderSerializer, FileRenderAllSerializer
+from .models import QTYRecorder
+from .models import ManualCyclecountModeModel
+from userprofile.models import Users
+from stock.views import StockBinViewSet
+from utils.md5 import Md5
+from staff.models import ListModel as staff
+
+class QTYRecorderViewSet(viewsets.ModelViewSet):
+    """
+        list:
+            Response a data list(all)
+
+    """
+    pagination_class = MyPageNumberPagination
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = QTYRecorderListFilter
+
+    def get_queryset(self):
+        if self.request.user:
+            return QTYRecorder.objects.filter(openid=self.request.auth.openid)
+        else:
+            return QTYRecorder.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.QTYRecorderSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+class CyclecountModeDayViewSet(viewsets.ModelViewSet):
+    """
+        retrieve:
+            Response a data list(get)
+
+        list:
+            Response a data list(all)
+
+        create:
+            Create a data line(post)
+
+        delete:
+            Delete a data line(delete)
+
+        partial_update:
+            Partial_update a data(patch:partial_update)
+
+        update:
+            Update a data(put:update)
+    """
+    pagination_class = None
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = Filter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            cur_date = timezone.now()
+            delt_date = relativedelta(days=1)
+            if id is None:
+                return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=0,
+                                                             update_time__gte=str((cur_date -delt_date).date()) + ' 00:00:00',
+                                                             update_time__lte=str((cur_date + delt_date).date()) + ' 00:00:00')
+            else:
+                return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=0,
+                                                             update_time__gte=str((cur_date - delt_date).date()) + ' 00:00:00',
+                                                             update_time__lte=str((cur_date + delt_date).date()) + ' 00:00:00', id=id)
+        else:
+            return CyclecountModeDayModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.CyclecountGetSerializer
+        elif self.action in ['create']:
+            return serializers.CyclecountPostSerializer
+        elif self.action in ['update']:
+            return serializers.CyclecountUpdateSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def create(self, request, *args, **kwargs):
+        data = self.request.data
+        for i in range(len(data)):
+            CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid,
+                                                  t_code=data[i]['t_code']).update(
+                physical_inventory=data[i]['physical_inventory'], cyclecount_status=1,
+                difference=data[i]['physical_inventory'] - data[i]['goods_qty'])
+        return Response({"detail": "success"}, status=200)
+
+    def update(self, request, *args, **kwargs):
+        data = self.request.data
+        for i in range(len(data)):
+            scan_count_data = self.get_queryset().filter(openid=self.request.auth.openid,
+                                                  t_code=data[i]['t_code']).first()
+            scan_count_data.physical_inventory = scan_count_data.physical_inventory + data[i]['physical_inventory']
+            scan_count_data.difference = data[i]['physical_inventory'] - data[i]['goods_qty']
+            scan_count_data.cyclecount_status = 1
+            scan_count_data.save()
+        return Response({"detail": "success"}, status=200)
+
+class CyclecountModeAllViewSet(viewsets.ModelViewSet):
+    """
+        list:
+            Response a data list(get)
+    """
+    pagination_class = MyPageNumberPagination
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = Filter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            date_choice = self.request.GET.get('create_time', '')
+            cur_time = timezone.now().date()
+            if date_choice:
+                if id is None:
+                    return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=1,
+                                                                 update_time__gte=str(date_choice) + ' 00:00:00',
+                                                                 update_time__lte=str(date_choice) + ' 23:59:59')
+                else:
+                    return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=1,
+                                                                 update_time__gte=str(date_choice) + ' 00:00:00',
+                                                                 update_time__lte=str(date_choice) + ' 23:59:59',
+                                                                 id=id)
+            else:
+                if id is None:
+                    return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=1,
+                                                                 update_time__gte=str(cur_time) + ' 00:00:00',
+                                                                 update_time__lte=str(cur_time) + ' 23:59:59')
+                else:
+                    return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=1,
+                                                                 update_time__gte=str(cur_time) + ' 00:00:00',
+                                                                 update_time__lte=str(cur_time) + ' 23:59:59',
+                                                                 id=id)
+        else:
+            return CyclecountModeDayModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.CyclecountGetSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+
+class FileDownloadView(viewsets.ModelViewSet):
+    renderer_classes = (FileRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time"]
+    filter_class = Filter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            cur_date = timezone.now()
+            delt_date = relativedelta(days=1)
+            if id is None:
+                return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=0,
+                                                             update_time__gte=str((cur_date -delt_date).date()) + ' 00:00:00')
+            else:
+                return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=0,
+                                                             update_time__gte=str((cur_date -delt_date).date()) + ' 00:00:00', id=id)
+        else:
+            return CyclecountModeDayModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.FileRenderSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def get_lang(self, data):
+        lang = self.request.META.get('HTTP_LANGUAGE')
+        if lang:
+            if lang == 'zh-hans':
+                return FileRenderCN().render(data)
+            else:
+                return FileRenderEN().render(data)
+        else:
+            return FileRenderEN().render(data)
+
+    def list(self, request, *args, **kwargs):
+        from datetime import datetime
+        dt = datetime.now()
+        data = (
+            FileRenderSerializer(instance).data
+            for instance in self.filter_queryset(self.get_queryset())
+        )
+        renderer = self.get_lang(data)
+        response = StreamingHttpResponse(
+            renderer,
+            content_type="text/csv"
+        )
+        response['Content-Disposition'] = "attachment; filename='cyclecount_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
+        return response
+
+class FileDownloadAllView(viewsets.ModelViewSet):
+    renderer_classes = (FileRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time"]
+    filter_class = Filter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+            id = self.get_project()
+            if self.request.user:
+                cur_date = timezone.now()
+                delt_date = relativedelta(days=1)
+                if id is None:
+                    return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=1,
+                                                                 update_time__gte=str((cur_date -delt_date).date()) + ' 00:00:00',
+                                                                 update_time__lte=str((cur_date + delt_date).date()) + ' 23:59:59')
+                else:
+                    return CyclecountModeDayModel.objects.filter(openid=self.request.auth.openid, cyclecount_status=1,
+                                                                 update_time__gte=str((cur_date - delt_date).date()) + ' 00:00:00',
+                                                                 update_time__lte=str((cur_date + delt_date).date()) + ' 23:59:59', id=id)
+            else:
+                return CyclecountModeDayModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.FileRenderSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def get_lang(self, data):
+        lang = self.request.META.get('HTTP_LANGUAGE')
+        if lang:
+            if lang == 'zh-hans':
+                return FileRenderCN().render(data)
+            else:
+                return FileRenderEN().render(data)
+        else:
+            return FileRenderEN().render(data)
+
+    def list(self, request, *args, **kwargs):
+        from datetime import datetime
+        dt = datetime.now()
+        data = (
+            FileRenderAllSerializer(instance).data
+            for instance in self.filter_queryset(self.get_queryset())
+        )
+        renderer = self.get_lang(data)
+        response = StreamingHttpResponse(
+            renderer,
+            content_type="text/csv"
+        )
+        response['Content-Disposition'] = "attachment; filename='cyclecountall_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
+        return response
+
+class GetGoodsCyclecountViewSet(StockBinViewSet):
+    """
+        list:
+            Response a data list(get)
+    """
+    pagination_class = None
+
+    def list(self, request, *args, **kwargs):
+        staff_name = staff.objects.filter(openid=self.request.auth.openid, id=self.request.META.get('HTTP_OPERATOR')).first().staff_name
+        queryset = self.filter_queryset(self.get_queryset())
+        goods_code = self.request.GET.get('goods_code', '')
+        for i in queryset:
+            if (d:=ManualCyclecountModeModel.objects.filter(cyclecount_status=0, bin_name=i.bin_name, goods_code=goods_code)).exists():
+                d.delete()
+            data = {
+                'openid': self.request.auth.openid,
+                'creater': staff_name,
+                'cyclecount_status': 0,
+                'bin_name': i.bin_name,
+                'goods_code': goods_code,
+                'goods_qty': i.goods_qty,
+                'physical_inventory': 0,
+                'difference': 0,
+                't_code': Md5.md5(goods_code)
+            }
+            serializer = serializers.ManualCyclecountPostSerializer(data=data)
+            serializer.is_valid(raise_exception=True)
+            serializer.save()
+        queryset = ManualCyclecountModeModel.objects.filter(goods_code=goods_code, cyclecount_status=0)
+        page = self.paginate_queryset(queryset)
+        if page is not None:
+            serializer = serializers.ManualCyclecountGetSerializer(instance=page, many=True)
+            return self.get_paginated_response(serializer.data)
+        serializer = serializers.ManualCyclecountGetSerializer(instance=queryset, many=True)
+        return Response(serializer.data)
+
+class ManualCyclecountViewSet(viewsets.ModelViewSet):
+    """
+        retrieve:
+            Response a data list(get)
+
+        list:
+            Response a data list(all)
+
+        create:
+            Create a data line(post)
+
+        delete:
+            Delete a data line(delete)
+
+        partial_update:
+            Partial_update a data(patch:partial_update)
+
+        update:
+            Update a data(put:update)
+    """
+    pagination_class = None
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = ManualFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            cur_date = timezone.now()
+            delt_date = relativedelta(days=1)
+            u = Users.objects.filter(vip=9).first()
+            if u is None:
+                superopenid = None
+            else:
+                superopenid = u.openid
+            query_dict = {
+                'cyclecount_status': 0,
+                'update_time__gte': str((cur_date - delt_date).date()) + ' 00:00:00',
+                'update_time__lte': str((cur_date + delt_date).date()) + ' 00:00:00'
+            }
+            if self.request.auth.openid != superopenid:
+                query_dict['openid'] = self.request.auth.openid
+            if id is not None:
+                query_dict['id'] = id
+            return ManualCyclecountModeModel.objects.filter(**query_dict)
+        else:
+            return ManualCyclecountModeModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.ManualCyclecountGetSerializer
+        elif self.action in ['create']:
+            return serializers.ManualCyclecountModeModel
+        elif self.action in ['update']:
+            return serializers.ManualCyclecountUpdateSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def create(self, request, *args, **kwargs):
+        data = self.request.data
+        for i in range(len(data)):
+            ManualCyclecountModeModel.objects.filter(openid=self.request.auth.openid, t_code=data[i]['t_code']).update(
+                physical_inventory=data[i]['physical_inventory'],
+                cyclecount_status=1,
+                difference=data[i]['physical_inventory'] - data[i]['goods_qty']
+            )
+        return Response({"detail": "success"}, status=200)
+
+    def update(self, request, *args, **kwargs):
+        data = self.request.data
+        for i in range(len(data)):
+            scan_count_data = self.get_queryset().filter(openid=self.request.auth.openid,
+                                                  t_code=data[i]['t_code']).first()
+            scan_count_data.physical_inventory = scan_count_data.physical_inventory + data[i]['physical_inventory']
+            scan_count_data.difference = data[i]['physical_inventory'] - data[i]['goods_qty']
+            scan_count_data.cyclecount_status = 1
+            scan_count_data.save()
+        return Response({"detail": "success"}, status=200)
+
+class ManualCyclecountRecorderViewSet(viewsets.ModelViewSet):
+    """
+        list:
+            Response a data list(get)
+    """
+    pagination_class = MyPageNumberPagination
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = ManualFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            date_choice = self.request.GET.get('create_time', '')
+            cur_time = timezone.now().date()
+            u = Users.objects.filter(vip=9).first()
+            if u is None:
+                superopenid = None
+            else:
+                superopenid = u.openid
+            query_dict = {
+                'cyclecount_status': 1
+            }
+            if self.request.auth.openid != superopenid:
+                query_dict['openid'] = self.request.auth.openid
+            if date_choice:
+                query_dict['update_time__gte'] = str(date_choice) + ' 00:00:00'
+                query_dict['update_time__lte'] = str(date_choice) + ' 23:59:59'
+            else:
+                query_dict['update_time__gte'] = str(cur_time) + ' 00:00:00'
+                query_dict['update_time__lte'] = str(cur_time) + ' 23:59:59'
+            if id is not None:
+                query_dict['id'] = id
+            return ManualCyclecountModeModel.objects.filter(**query_dict)
+        else:
+            return ManualCyclecountModeModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.ManualCyclecountGetSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+class ManualFileDownloadView(viewsets.ModelViewSet):
+    renderer_classes = (FileRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time"]
+    filter_class = ManualFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            cur_date = timezone.now()
+            delt_date = relativedelta(days=1)
+            u = Users.objects.filter(vip=9).first()
+            if u is None:
+                superopenid = None
+            else:
+                superopenid = u.openid
+            query_dict = {
+                'cyclecount_status': 0,
+                'update_time__gte': str((cur_date - delt_date).date()) + ' 00:00:00'
+            }
+            if self.request.auth.openid != superopenid:
+                query_dict['openid'] = self.request.auth.openid
+            if id is not None:
+                query_dict['id'] = id
+            return ManualCyclecountModeModel.objects.filter(**query_dict)
+        else:
+            return ManualCyclecountModeModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.ManualFileRenderSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def get_lang(self, data):
+        lang = self.request.META.get('HTTP_LANGUAGE')
+        if lang:
+            if lang == 'zh-hans':
+                return FileRenderCN().render(data)
+            else:
+                return FileRenderEN().render(data)
+        else:
+            return FileRenderEN().render(data)
+
+    def list(self, request, *args, **kwargs):
+        from datetime import datetime
+        dt = datetime.now()
+        data = (
+            serializers.ManualFileRenderSerializer(instance).data
+            for instance in self.filter_queryset(self.get_queryset())
+        )
+        renderer = self.get_lang(data)
+        response = StreamingHttpResponse(
+            renderer,
+            content_type="text/csv"
+        )
+        response['Content-Disposition'] = "attachment; filename='manualcyclecount_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
+        return response

+ 0 - 0
dashboard/__init__.py


+ 3 - 0
dashboard/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 5 - 0
dashboard/apps.py

@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class DashboardConfig(AppConfig):
+    name = 'dashboard'

+ 0 - 0
dashboard/migrations/__init__.py


+ 3 - 0
dashboard/models.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 3 - 0
dashboard/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 7 - 0
dashboard/urls.py

@@ -0,0 +1,7 @@
+from django.urls import path, re_path
+from . import views
+
+urlpatterns = [
+path(r'receipts/', views.ReceiptsViewSet.as_view({"get": "list"}), name="receipts"),
+path(r'sales/', views.SalesViewSet.as_view({"get": "list"}), name="sales")
+]

+ 202 - 0
dashboard/views.py

@@ -0,0 +1,202 @@
+from rest_framework import viewsets
+from asn.models import AsnDetailModel
+from dn.models import DnDetailModel
+from asn import serializers as asnserializers
+from dn import serializers as dnserializers
+from utils.page import MyPageNumberPagination
+from utils.datasolve import sumOfList
+from utils.fbmsg import FBMsg
+from utils.md5 import Md5
+from rest_framework.filters import OrderingFilter
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework.response import Response
+from asn.filter import AsnDetailFilter
+from dn.filter import DnDetailFilter
+from rest_framework.exceptions import APIException
+from django.shortcuts import render
+from dateutil.relativedelta import relativedelta
+from django.db.models.functions import TruncMonth,TruncYear,ExtractDay,ExtractMonth
+from django.db.models import Count
+from django.db import connection
+from django.db.models import Q
+from django.db.models import Sum
+import re
+from django.utils import timezone
+
+class ReceiptsViewSet(viewsets.ModelViewSet):
+    """
+        list:
+            Response a data list(all)
+    """
+    pagination_class = None
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = AsnDetailFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return AsnDetailModel.objects.filter(openid=self.request.auth.openid, asn_status__gte=4,
+                                                     create_time__gte=timezone.now().date() - relativedelta(days=14),
+                                                     is_delete=False)
+            else:
+                return AsnDetailModel.objects.filter(openid=self.request.auth.openid, asn_status__gte=4,
+                                                     create_time__gte=timezone.now().date() - relativedelta(days=14),
+                                                     id=id, is_delete=False)
+        else:
+            return AsnDetailModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return asnserializers.ASNDetailGetSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def notice_lang(self):
+        lang = self.request.META.get('HTTP_LANGUAGE')
+        return lang
+
+    def list(self, request, *args, **kwargs):
+        qs = self.get_queryset()
+        context = {}
+        dataset = {}
+        dimensions = ['product']
+        source = []
+        series = []
+        bar_charts = {
+            "type": 'bar',
+            "barWidth": '4%',
+            "barGap": '60%',
+            "barCategoryGap": '10%',
+            "itemStyle": {
+              "normal": {
+                "label": {
+                  "show": "true",
+                  "position": "top"
+                }
+              }
+            }
+          }
+        receipt_res = qs.annotate(month=ExtractMonth('create_time'), day=ExtractDay('create_time')) \
+            .values('month', 'day').order_by('month', 'day').annotate(number=Sum('goods_cost'))
+        # qty_res = qs.values('goods_code').order_by('goods_code').annotate(number=Sum('goods_qty'))
+        # rank_res = qs.values('goods_code').order_by('goods_code').annotate(number=Sum('goods_cost'))
+        receipt_res_dict = {
+        }
+        # qty_res_dict = {
+        # }
+        # rank_res_dict = {
+        # }
+        for i in receipt_res:
+            series.append(bar_charts)
+            dimensions.append("%s-%s" % (i['month'], i['day']))
+            receipt_res_dict.update({"%s-%s" % (i['month'], i['day']): round(i['number'], 2)})
+        # for i in qty_res:
+        #     qty_res_dict.update({i['goods_code']: i['number']})
+        # for i in rank_res:
+        #     rank_res_dict.update({i['goods_code']: i['number']})
+        source.append(receipt_res_dict)
+        # data_list.append(qty_res_dict)
+        # data_list.append(rank_res_dict)
+        dataset['source'] = source
+        dataset['dimensions'] = dimensions
+        context['dataset'] = dataset
+        context['series'] = series
+        return Response(context)
+
+class SalesViewSet(viewsets.ModelViewSet):
+    """
+        list:
+            Response a data list(all)
+    """
+    pagination_class = None
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = DnDetailFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return DnDetailModel.objects.filter(openid=self.request.auth.openid, dn_status__gte=4,
+                                                    create_time__gte=timezone.now().date() - relativedelta(days=14),
+                                                    is_delete=False)
+            else:
+                return DnDetailModel.objects.filter(openid=self.request.auth.openid, dn_status__gte=4,
+                                                    create_time__gte=timezone.now().date() - relativedelta(days=14),
+                                                    id=id, is_delete=False)
+        else:
+            return DnDetailModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return dnserializers.DNDetailGetSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def notice_lang(self):
+        lang = self.request.META.get('HTTP_LANGUAGE')
+        return lang
+
+    def list(self, request, *args, **kwargs):
+        qs = self.get_queryset()
+        context = {}
+        dataset = {}
+        dimensions = ['product']
+        source = []
+        series = []
+        bar_charts = {
+            "type": 'bar',
+            "barWidth": '4%',
+            "barGap": '60%',
+            "barCategoryGap": '10%',
+            "itemStyle": {
+              "normal": {
+                "label": {
+                  "show": "true",
+                  "position": "top"
+                }
+              }
+            }
+          }
+        receipt_res = qs.annotate(month=ExtractMonth('create_time'), day=ExtractDay('create_time')) \
+            .values('month', 'day').order_by('month', 'day').annotate(number=Sum('goods_cost'))
+        # qty_res = qs.values('goods_code').order_by('goods_code').annotate(number=Sum('goods_qty'))
+        # rank_res = qs.values('goods_code').order_by('goods_code').annotate(number=Sum('goods_cost'))
+        receipt_res_dict = {
+        }
+        # qty_res_dict = {
+        # }
+        # rank_res_dict = {
+        # }
+        for i in receipt_res:
+            series.append(bar_charts)
+            dimensions.append("%s-%s" % (i['month'], i['day']))
+            receipt_res_dict.update({"%s-%s" % (i['month'], i['day']): i['number']})
+        # for i in qty_res:
+        #     qty_res_dict.update({i['goods_code']: i['number']})
+        # for i in rank_res:
+        #     rank_res_dict.update({i['goods_code']: i['number']})
+        source.append(receipt_res_dict)
+        # data_list.append(qty_res_dict)
+        # data_list.append(rank_res_dict)
+        dataset['source'] = source
+        dataset['dimensions'] = dimensions
+        context['dataset'] = dataset
+        context['series'] = series
+        return Response(context)

+ 40 - 0
docker-compose.yml

@@ -0,0 +1,40 @@
+version: '3.9'
+networks:
+  basic:
+services:
+  front:
+    # build:
+    #   context: ./
+    #   dockerfile: Dockerfile
+    #   target: front
+    container_name: greaterwms_front
+    image: greaterwms/greaterwms:front
+    restart: always
+    privileged: true
+    ports:
+      - '8080:8080'
+    depends_on:
+      - backend
+    volumes:
+      - ./templates:/GreaterWMS/templates:rw
+      - ./web_start.sh:/GreaterWMS/templates/web_start.sh
+    networks:
+      - basic
+  backend:
+    # build:
+    #   context: ./
+    #   dockerfile: Dockerfile
+    #   target: backend
+    container_name: greaterwms_backend
+    image: greaterwms/greaterwms:backend
+    restart: always
+    privileged: true
+    volumes:
+      - ./:/GreaterWMS/:rw
+      - ./supervisord.conf:/etc/supervisor/supervisord.conf
+    ports:
+      - '8008:8008'
+    environment:
+      PYTHONUNBUFFERED: 1
+    networks:
+      - basic

+ 67 - 0
greaterwms/__init__.py

@@ -0,0 +1,67 @@
+import mimetypes, os, requests, django
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'greaterwms.settings')
+os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
+django.setup()
+from django.conf import settings
+import pandas as pd
+from pathlib import Path
+
+mimetypes.add_type("text/css", ".css", True)
+mimetypes.add_type("text/javascript", ".js", True)
+
+win32_folder = os.path.exists(os.path.join(settings.BASE_DIR, 'media/' + "/win32"))
+linux_folder = os.path.exists(os.path.join(settings.BASE_DIR, 'media/' + "/linux"))
+darwin_folder = os.path.exists(os.path.join(settings.BASE_DIR, 'media/' + "/darwin"))
+upload_folder = os.path.exists(os.path.join(settings.BASE_DIR, 'media/' + "/upload_example"))
+if not win32_folder:
+    os.makedirs(os.path.join(settings.BASE_DIR, 'media/' + "/win32"))
+if not linux_folder:
+    os.makedirs(os.path.join(settings.BASE_DIR, 'media/' + "/linux"))
+if not darwin_folder:
+    os.makedirs(os.path.join(settings.BASE_DIR, 'media/' + "/darwin"))
+if not upload_folder:
+    os.makedirs(os.path.join(settings.BASE_DIR, 'media/' + "/upload_example"))
+
+customer_cn_path = os.path.join(settings.BASE_DIR, 'media/' + "/upload_example/customer_cn.xlsx")
+customer_en_path = os.path.join(settings.BASE_DIR, 'media/' + "/upload_example/customer_en.xlsx")
+goodslist_cn_path = os.path.join(settings.BASE_DIR, 'media/' + "/upload_example/goodslist_cn.xlsx")
+goodslist_en_path = os.path.join(settings.BASE_DIR, 'media/' + "/upload_example/goodslist_en.xlsx")
+supplier_cn_path = os.path.join(settings.BASE_DIR, 'media/' + "/upload_example/supplier_cn.xlsx")
+supplier_en_path = os.path.join(settings.BASE_DIR, 'media/' + "/upload_example/supplier_en.xlsx")
+customer_cn_file = os.path.exists(customer_cn_path)
+customer_en_file = os.path.exists(customer_en_path)
+goodslist_cn_file = os.path.exists(goodslist_cn_path)
+goodslist_en_file = os.path.exists(goodslist_en_path)
+supplier_cn_file = os.path.exists(supplier_cn_path)
+supplier_en_file = os.path.exists(supplier_en_path)
+if not customer_cn_file:
+    customer_cn = pd.DataFrame({"客户名称": [], "客户城市": [] ,"详细地址": [], "联系电话": [], "负责人": [], "客户等级": []})
+    df = customer_cn.set_index("客户名称")
+    df.to_excel(customer_cn_path)
+
+if not customer_en_file:
+    customer_en = pd.DataFrame({"Customer Name": [], "Customer City": [], "Customer Address": [], "Customer Contact": [], "Customer Manager": [], "Customer Level": []})
+    df = customer_en.set_index("Customer Name")
+    df.to_excel(customer_en_path)
+
+if not goodslist_cn_file:
+    goodslist_cn = pd.DataFrame({"商品编码": [], "商品描述": [], "商品供应商": [], "商品单位重量": [], "商品单位长度": [], "商品单位宽度": [], "商品单位高度": [],  "最小单位体积": [], "商品单位": [], "商品类别": [], "商品品牌": [], "商品颜色": [], "商品形状": [], "商品规格": [], "商品产地": [], "商品成本": [], "商品价格": []})
+    df = goodslist_cn.set_index("商品编码")
+    df.to_excel(goodslist_cn_path)
+
+if not goodslist_en_file:
+    goodslist_en = pd.DataFrame({"Goods Code": [], "Goods Description": [], "Goods Supplier": [], "Goods Weight": [], "Goods Width": [], "Goods Depth": [], "Goods Height": [],  "Unit Volume": [], "Goods Unit": [], "Goods Class": [], "Goods Brand": [], "Goods Color": [], "Goods Shape": [], "Goods Specs": [], "Goods Origin": [], "Goods Cost": [], "Goods Price": []})
+    df = goodslist_en.set_index("Goods Code")
+    df.to_excel(goodslist_en_path)
+
+if not supplier_cn_file:
+    supplier_cn = pd.DataFrame({"供应商名称": [], "供应商城市": [] ,"详细地址": [], "联系电话": [], "负责人": [], "供应商等级": []})
+    df = supplier_cn.set_index("供应商名称")
+    df.to_excel(supplier_cn_path)
+
+if not supplier_en_file:
+    supplier_en = pd.DataFrame({"Supplier Name": [], "Supplier City": [] ,"Supplier Address": [], "Supplier Contact": [], "Supplier Manager": [], "Supplier Level": []})
+    df = supplier_en.set_index("Supplier Name")
+    df.to_excel(supplier_en_path)
+
+print('Welcome To GreaterWMS')

+ 19 - 0
greaterwms/asgi.py

@@ -0,0 +1,19 @@
+import os
+
+from django.core.asgi import get_asgi_application
+from utils.websocket import websocket_application
+from asgihandler.core import ASGIHandler
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'greaterwms.settings')
+
+http_application = get_asgi_application()
+
+
+async def application(scope, receive, send):
+    if scope['type'] in ['http', 'https']:
+        ASGIHandler.asgi_get_handler(scope)
+        await http_application(scope, receive, send)
+    elif scope['type'] in ['websocket']:
+        await websocket_application(scope, receive, send)
+    else:
+        raise Exception('Unknown Type' + scope['type'])
+

+ 356 - 0
greaterwms/settings.py

@@ -0,0 +1,356 @@
+from pathlib import Path
+import os
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+from django.core.management.utils import get_random_secret_key
+SECRET_KEY = get_random_secret_key()
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'staff.apps.StaffConfig',
+    'userprofile.apps.UserprofileConfig',
+    'userregister.apps.UserregisterConfig',
+    'userlogin.apps.UserloginConfig',
+    'company.apps.CompanyConfig',
+    'supplier.apps.SupplierConfig',
+    'asn.apps.AsnConfig',
+    'dn.apps.DnConfig',
+    'binset.apps.BinsetConfig',
+    'binsize.apps.BinsizeConfig',
+    'binproperty.apps.BinpropertyConfig',
+    'customer.apps.CustomerConfig',
+    'capital.apps.CapitalConfig',
+    'cyclecount.apps.CyclecountConfig',
+    'dashboard.apps.DashboardConfig',
+    'warehouse.apps.WarehouseConfig',
+    'goods.apps.GoodsConfig',
+    'goodsunit.apps.GoodsunitConfig',
+    'goodsclass.apps.GoodsclassConfig',
+    'goodscolor.apps.GoodscolorConfig',
+    'goodsbrand.apps.GoodsbrandConfig',
+    'goodsshape.apps.GoodsshapeConfig',
+    'goodsspecs.apps.GoodsspecsConfig',
+    'goodsorigin.apps.GoodsoriginConfig',
+    'payment.apps.PaymentConfig',
+    'driver.apps.DriverConfig',
+    'stock.apps.StockConfig',
+    'throttle.apps.ThrottleConfig',
+    'uploadfile.apps.UploadfileConfig',
+    'scanner.apps.ScannerConfig',
+    'rest_framework',
+    'django_filters',
+    'corsheaders',
+    'drf_spectacular',
+    'drf_spectacular_sidecar'
+]
+
+MIDDLEWARE = [
+    'corsheaders.middleware.CorsMiddleware',
+    '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',
+]
+
+ROOT_URLCONF = 'greaterwms.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [BASE_DIR / 'templates']
+        ,
+        '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 = 'greaterwms.wsgi.application'
+CSRF_COOKIE_SAMESITE = None
+
+# Database
+# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
+# update
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': BASE_DIR / 'db.sqlite3',
+        'OPTIONS': {
+            'timeout': 20,
+        }
+    }
+}
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+# Password validation
+# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/3.1/topics/i18n/
+
+LANGUAGE_CODE = 'zh-hans'
+
+TIME_ZONE = 'Asia/Shanghai'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = False
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/3.1/howto/static-files/
+
+STATIC_URL = '/static/'
+STATIC_ROOT = os.path.join(BASE_DIR, 'static_new').replace('\\', '/')
+STATICFILES_DIRS = [
+    os.path.join(BASE_DIR, 'static').replace('\\', '/'),
+]
+
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
+
+SPECTACULAR_SETTINGS = {
+    'TITLE': 'GreaterWMS',
+    'DESCRIPTION': 'GreaterWMS API Documents',
+    'VERSION': '2.1.48',
+    'SERVE_INCLUDE_SCHEMA': False,
+    # OTHER SETTINGS
+    'SWAGGER_UI_DIST': 'SIDECAR',  # shorthand to use the sidecar instead
+    'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
+    'REDOC_DIST': 'SIDECAR',
+    # OTHER SETTINGS
+}
+
+REST_FRAMEWORK = {
+    # AttributeError: ‘AutoSchema’ object has no attribute ‘get_link’
+    # DEFAULT SET:
+    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
+    # 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',
+    # EXCEPTION:
+    'EXCEPTION_HANDLER': 'utils.my_exceptions.custom_exception_handler',
+    # Base API policies:
+    'DEFAULT_RENDERER_CLASSES': [
+        'rest_framework.renderers.JSONRenderer',
+        'rest_framework_csv.renderers.CSVRenderer',
+        #'rest_framework.renderers.BrowsableAPIRenderer',
+    ],
+    'DEFAULT_PARSER_CLASSES': [
+        'rest_framework.parsers.JSONParser',
+        'rest_framework.parsers.FormParser',
+        'rest_framework.parsers.MultiPartParser'
+    ],
+    'DEFAULT_AUTHENTICATION_CLASSES': ['utils.auth.Authtication', ],
+    'DEFAULT_PERMISSION_CLASSES': ["utils.permission.Normalpermission", ],
+    'DEFAULT_THROTTLE_CLASSES': ['utils.throttle.VisitThrottle', ],
+    # 'DEFAULT_THROTTLE_RATES': ['utils.throttle.VisitThrottle', ],
+    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
+    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
+    'DEFAULT_VERSIONING_CLASS': None,
+    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+    # 'PAGE_SIZE': 1,  # 默认 None
+    'DEFAULT_FILTER_BACKENDS': [
+        'django_filters.rest_framework.DjangoFilterBackend',
+        # 'django_filters.rest_framework.backends.DjangoFilterBackend',
+    ],
+    'SEARCH_PARAM': 'search',
+    'ORDERING_PARAM': 'ordering',
+    'NUM_PROXIES': None,
+    # Versioning:
+    'DEFAULT_VERSION': None,
+    'ALLOWED_VERSIONS': None,
+    'VERSION_PARAM': 'version',
+    # Authentication:
+    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
+    'UNAUTHENTICATED_TOKEN': None,
+    # View configuration:
+    'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
+    'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',
+    'NON_FIELD_ERRORS_KEY': 'non_field_errors',
+    # Testing
+    'TEST_REQUEST_RENDERER_CLASSES': [
+        'rest_framework.renderers.MultiPartRenderer',
+        'rest_framework.renderers.JSONRenderer'
+    ],
+    'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',
+    # Hyperlink settings
+    'URL_FORMAT_OVERRIDE': 'format',
+    'FORMAT_SUFFIX_KWARG': 'format',
+    'URL_FIELD_NAME': 'url',
+    # Encoding
+    'UNICODE_JSON': True,
+    'COMPACT_JSON': True,
+    'STRICT_JSON': True,
+    'COERCE_DECIMAL_TO_STRING': True,
+    'UPLOADED_FILES_USE_URL': True,
+    # Browseable API
+    'HTML_SELECT_CUTOFF': 1000,
+    'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",
+    # Schemas
+    'SCHEMA_COERCE_PATH_PK': True,
+    'SCHEMA_COERCE_METHOD_NAMES': {
+        'retrieve': 'read',
+        'destroy': 'delete'
+    },
+}
+
+SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
+ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
+if not os.path.exists(os.path.join(BASE_DIR, "logs")):
+    os.makedirs(os.path.join(BASE_DIR, "logs"))
+
+STANDARD_LOG_FORMAT = (
+    "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
+)
+CONSOLE_LOG_FORMAT = (
+    "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
+)
+
+LOGGING = {
+    "version": 1,
+    "disable_existing_loggers": False,
+    "formatters": {
+        "standard": {"format": STANDARD_LOG_FORMAT},
+        "console": {
+            "format": CONSOLE_LOG_FORMAT,
+            "datefmt": "%Y-%m-%d %H:%M:%S",
+        },
+        "file": {
+            "format": CONSOLE_LOG_FORMAT,
+            "datefmt": "%Y-%m-%d %H:%M:%S",
+        },
+    },
+    "handlers": {
+        "file": {
+            "level": "INFO",
+            "class": "logging.handlers.RotatingFileHandler",
+            "filename": SERVER_LOGS_FILE,
+            "maxBytes": 1024 * 1024 * 100,
+            "backupCount": 5,
+            "formatter": "standard",
+            "encoding": "utf-8",
+        },
+        "error": {
+            "level": "ERROR",
+            "class": "logging.handlers.RotatingFileHandler",
+            "filename": ERROR_LOGS_FILE,
+            "maxBytes": 1024 * 1024 * 100,
+            "backupCount": 3,
+            "formatter": "standard",
+            "encoding": "utf-8",
+        },
+        "console": {
+            "level": "INFO",
+            "class": "logging.StreamHandler",
+            "formatter": "console",
+        },
+    },
+    "loggers": {
+        "django": {
+            "handlers": ["console", "error", "file"],
+            "level": "INFO",
+            "propagate": False,
+        },
+        "scripts": {
+            "handlers": ["console", "error", "file"],
+            "level": "INFO",
+            "propagate": False,
+        },
+        "django.db.backends": {
+            "handlers": [],
+            "propagate": True,
+            "level": "INFO",
+        },
+    },
+}
+
+CORS_ALLOW_CREDENTIALS = True
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_ORIGIN_WHITELIST = ()
+
+CORS_ALLOW_METHODS = (
+    'DELETE',
+    'GET',
+    'OPTIONS',
+    'PATCH',
+    'POST',
+    'PUT',
+    'VIEW',
+)
+
+CORS_ALLOW_HEADERS = (
+    'accept',
+    'accept-encoding',
+    'authorization',
+    'content-type',
+    'dnt',
+    'origin',
+    'user-agent',
+    'x-csrftoken',
+    'x-requested-with',
+    'token',
+    'language',
+    'operator',
+    'device',
+    'app-id',
+    'event-sign'
+)
+
+LAZY_RENDERING = True
+NATIVE_SCROLLBARS = True
+
+ALLOCATION_SECONDS = 1
+GET_THROTTLE = 500
+POST_THROTTLE = 500
+PUT_THROTTLE = 500
+PATCH_THROTTLE = 500
+DELETE_THROTTLE = 500
+
+JWT_TIME = 60 * 60 * 24 * 365 * 20

+ 63 - 0
greaterwms/urls.py

@@ -0,0 +1,63 @@
+from django.contrib import admin
+from django.conf import settings
+from django.urls import path, include, re_path
+from django.views.generic.base import TemplateView
+from django.contrib.staticfiles.views import serve
+from django.views.static import serve as static_serve
+from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
+from . import views
+
+
+def return_static(request, path, insecure=True, **kwargs):
+  return serve(request, path, insecure, **kwargs)
+
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+    path('', TemplateView.as_view(template_name='dist/spa/index.html')),
+    path('myip/', views.myip, name='myip'),
+    path('asn/', include('asn.urls')),
+    path('dn/', include('dn.urls')),
+    path('staff/', include('staff.urls')),
+    path('binset/', include('binset.urls')),
+    path('binsize/', include('binsize.urls')),
+    path('binproperty/', include('binproperty.urls')),
+    path('capital/', include('capital.urls')),
+    path('driver/', include('driver.urls')),
+    path('stock/', include('stock.urls')),
+    path('company/', include('company.urls')),
+    path('cyclecount/', include('cyclecount.urls')),
+    path('dashboard/', include('dashboard.urls')),
+    path('supplier/', include('supplier.urls')),
+    path('customer/', include('customer.urls')),
+    path('warehouse/', include('warehouse.urls')),
+    path('goods/', include('goods.urls')),
+    path('goodsunit/', include('goodsunit.urls')),
+    path('goodsclass/', include('goodsclass.urls')),
+    path('goodscolor/', include('goodscolor.urls')),
+    path('goodsbrand/', include('goodsbrand.urls')),
+    path('goodsshape/', include('goodsshape.urls')),
+    path('goodsspecs/', include('goodsspecs.urls')),
+    path('goodsorigin/', include('goodsorigin.urls')),
+    path('scanner/', include('scanner.urls')),
+    path('payment/', include('payment.urls')),
+    path('login/', include('userlogin.urls')),
+    path('register/', include('userregister.urls')),
+    path('uploadfile/', include('uploadfile.urls')),
+    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
+    re_path(r'^favicon\.ico$', views.favicon, name='favicon'),
+    re_path('^css/.*$', views.css, name='css'),
+    re_path('^js/.*$', views.js, name='js'),
+    re_path('^statics/.*$', views.statics, name='statics'),
+    re_path('^fonts/.*$', views.fonts, name='fonts'),
+    re_path(r'^robots.txt', views.robots, name='robots'),
+    re_path(r'^media/(?P<path>.*)$', static_serve, {'document_root': settings.MEDIA_ROOT}),
+    re_path(r'^static/(?P<path>.*)$', return_static, name='static')
+]
+
+urlpatterns += [
+    path('api/', SpectacularAPIView.as_view(), name='schema'),
+    # Optional UI:
+    path('api/debug/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
+    path('api/docs/', SpectacularRedocView.as_view(url_name='schema'), name='docs'),
+]

+ 56 - 0
greaterwms/views.py

@@ -0,0 +1,56 @@
+from django.http import StreamingHttpResponse, JsonResponse
+from django.conf import settings
+from wsgiref.util import FileWrapper
+from rest_framework.exceptions import APIException
+import mimetypes, os
+
+def robots(request):
+    path = settings.BASE_DIR + request.path_info
+    content_type, encoding = mimetypes.guess_type(path)
+    resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
+    resp['Cache-Control'] = "max-age=864000000000"
+    return resp
+
+def favicon(request):
+    path = str(settings.BASE_DIR) + '/static/img/logo.png'
+    content_type, encoding = mimetypes.guess_type(path)
+    resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
+    resp['Cache-Control'] = "max-age=864000000000"
+    return resp
+
+def css(request):
+    path = str(settings.BASE_DIR) + '/templates/dist/spa' + request.path_info
+    content_type, encoding = mimetypes.guess_type(path)
+    resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
+    resp['Cache-Control'] = "max-age=864000000000"
+    return resp
+
+def js(request):
+    path = str(settings.BASE_DIR) + '/templates/dist/spa' + request.path_info
+    content_type, encoding = mimetypes.guess_type(path)
+    resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
+    resp['Cache-Control'] = "max-age=864000000000"
+    return resp
+
+def statics(request):
+    path = str(settings.BASE_DIR) + '/templates/dist/spa' + request.path_info
+    content_type, encoding = mimetypes.guess_type(path)
+    resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
+    resp['Cache-Control'] = "max-age=864000000000"
+    return resp
+
+def fonts(request):
+    path = str(settings.BASE_DIR) + '/templates/dist/spa' + request.path_info
+    content_type, encoding = mimetypes.guess_type(path)
+    resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
+    resp['Cache-Control'] = "max-age=864000000000"
+    return resp
+
+def myip(request):
+    import socket
+    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    s.connect(('8.8.8.8', 80))
+    print(s.getsockname()[0])
+    ip = s.getsockname()[0]
+    s.close()
+    return JsonResponse({"ip": ip})

+ 16 - 0
greaterwms/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for django_wms project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'greaterwms.settings')
+
+application = get_wsgi_application()

+ 22 - 0
manage.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'greaterwms.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 0 - 0
media/__init__.py


+ 97 - 0
nginx.conf

@@ -0,0 +1,97 @@
+
+user root;
+worker_processes auto;
+
+events {
+    worker_connections  1024;
+}
+
+http {
+    include         mime.types;
+    default_type    application/octet-stream;
+    sendfile        on;
+    gzip            on;
+    gzip_min_length 1k;
+    gzip_comp_level 4;
+    gzip_types      text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
+    gzip_disable    "MSIE [1-6]\.";
+    gzip_vary       on;
+	proxy_redirect off;
+	proxy_set_header Host $host;
+	proxy_set_header  https $https;
+	proxy_set_header X-Real-IP $remote_addr;
+	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+	client_max_body_size 75M;
+	client_body_buffer_size 256k;
+	client_header_timeout 3m;
+	client_body_timeout 3m;
+	send_timeout 3m;
+	proxy_connect_timeout 300s;
+	proxy_read_timeout 300s;
+	proxy_send_timeout 300s;
+	proxy_buffer_size 64k;
+	proxy_buffers 4 32k;
+	proxy_busy_buffers_size 64k;
+	proxy_temp_file_write_size 64k;
+	proxy_ignore_client_abort on;
+
+	upstream GreaterWMS{
+  server 127.0.0.1:8008;
+}
+	server {
+	listen      80;
+	server_name {{ Domin Name }};
+	rewrite ^(.*)$ https://{{ Domin Name }}$1;
+
+	}
+
+      server {
+	listen      443 ssl;
+
+    server_name  {{ Domin Name }};
+	root /path/to/GreaterWMS;
+	charset utf-8;
+	add_header X-Frame-Options "SAMEORIGIN";
+	add_header X-XSS-Protection "1; mode=block";
+	add_header X-Content-Type-Options "nosniff";
+
+	client_max_body_size 75M;
+
+ 	ssl_certificate   /path/to/GreaterWMS.pem;
+ 	ssl_certificate_key  /path/to/GreaterWMS.key;
+ 	ssl_session_timeout 5m;
+ 	ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
+ 	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+	ssl_prefer_server_ciphers on;
+
+    access_log off;
+	error_log  /path/to/GreaterWMS/greaterwms-error.log error;
+
+	 location /websocket/ {
+       proxy_pass http://GreaterWMS/;
+			 proxy_read_timeout 60s;
+       proxy_set_header Host $host;
+       proxy_set_header X-Real_IP $remote_addr;
+       proxy_set_header X-Forwarded-for $remote_addr;
+       proxy_http_version 1.1;
+       proxy_set_header Upgrade $http_upgrade;
+       proxy_set_header Connection 'Upgrade';
+    }
+
+    location / {
+        #root   html;
+        #index  testssl.html index.html index.htm;
+       proxy_redirect off;
+       proxy_set_header Host $host;
+       proxy_set_header X-Real-IP $remote_addr;
+       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+       proxy_pass http://127.0.0.1:8008/;
+    }
+		location /static/ {
+			alias /path/to/GreaterWMS/static_new/;
+		}
+	location /media/{
+		alias /path/to/GreaterWMS/media/;
+	}
+    }
+}

+ 52 - 0
requirements.txt

@@ -0,0 +1,52 @@
+asgihandler==0.5.9
+asgiref==3.5.2
+attrs==23.1.0
+autobahn==23.1.2
+Automat==22.10.0
+certifi==2022.9.24
+cffi==1.15.1
+charset-normalizer==2.1.1
+constantly==15.1.0
+cryptography==40.0.2
+daphne==4.0.0
+Django==4.1.2
+django-cors-headers==3.13.0
+django-filter==2.4.0
+djangorestframework==3.14.0
+djangorestframework-csv==2.1.1
+drf-spectacular==0.26.2
+drf-spectacular-sidecar==2023.6.1
+et-xmlfile==1.1.0
+h2==4.1.0
+hpack==4.0.0
+hyperframe==6.0.1
+hyperlink==21.0.0
+idna==3.4
+incremental==22.10.0
+inflection==0.5.1
+jsonschema==4.17.3
+numpy==1.23.4
+openpyxl==3.0.10
+pandas==1.5.1
+priority==1.3.0
+pyasn1==0.5.0
+pyasn1-modules==0.3.0
+pycparser==2.21
+PyJWT==2.8.0
+pyOpenSSL==23.1.1
+pyrsistent==0.19.3
+python-dateutil==2.8.2
+pytz==2022.5
+PyYAML==6.0
+requests==2.28.1
+service-identity==21.1.0
+six==1.16.0
+sqlparse==0.4.3
+Twisted==22.10.0
+txaio==23.1.1
+typing_extensions==4.6.2
+tzdata==2022.5
+unicodecsv==0.14.1
+uritemplate==4.1.1
+urllib3==1.26.12
+zope.interface==6.0

+ 0 - 0
static/__init__.py


BIN
static/img/GreaterWMS.png


BIN
static/img/GreaterWMS_en.png


BIN
static/img/Katie.jpg


BIN
static/img/alipay.jpg


BIN
static/img/contact.png


BIN
static/img/dongtai.png


BIN
static/img/dongtai1.png


BIN
static/img/github.png


BIN
static/img/logo.png


BIN
static/img/logout.png


BIN
static/img/mobile_dn.jpg


BIN
static/img/mobile_dn_en.jpg


BIN
static/img/mobile_equ.jpg


BIN
static/img/mobile_equ_en.jpg


BIN
static/img/mobile_splash.jpg


BIN
static/img/money.png


BIN
static/img/photo.png


BIN
static/img/profile.png


BIN
static/img/register.png


BIN
static/img/user.jpg


BIN
static/img/user.png


BIN
static/img/video.png


BIN
static/img/wechat.jpg


+ 0 - 0
static_new/__init__.py


+ 275 - 0
static_new/admin/css/autocomplete.css

@@ -0,0 +1,275 @@
+select.admin-autocomplete {
+    width: 20em;
+}
+
+.select2-container--admin-autocomplete.select2-container {
+    min-height: 30px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single,
+.select2-container--admin-autocomplete .select2-selection--multiple {
+    min-height: 30px;
+    padding: 0;
+}
+
+.select2-container--admin-autocomplete.select2-container--focus .select2-selection,
+.select2-container--admin-autocomplete.select2-container--open .select2-selection {
+    border-color: var(--body-quiet-color);
+    min-height: 30px;
+}
+
+.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--single,
+.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--single {
+    padding: 0;
+}
+
+.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--multiple,
+.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--multiple {
+    padding: 0;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single {
+    background-color: var(--body-bg);
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
+    color: var(--body-fg);
+    line-height: 30px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
+    color: var(--body-quiet-color);
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b {
+    border-color: #888 transparent transparent transparent;
+    border-style: solid;
+    border-width: 5px 4px 0 4px;
+    height: 0;
+    left: 50%;
+    margin-left: -4px;
+    margin-top: -2px;
+    position: absolute;
+    top: 50%;
+    width: 0;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__clear {
+    float: left;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+    left: 1px;
+    right: auto;
+}
+
+.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
+    background-color: var(--darkened-bg);
+    cursor: default;
+}
+
+.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single .select2-selection__clear {
+    display: none;
+}
+
+.select2-container--admin-autocomplete.select2-container--open .select2-selection--single .select2-selection__arrow b {
+    border-color: transparent transparent #888 transparent;
+    border-width: 0 4px 5px 4px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple {
+    background-color: var(--body-bg);
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    cursor: text;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered {
+    box-sizing: border-box;
+    list-style: none;
+    margin: 0;
+    padding: 0 10px 5px 5px;
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
+    list-style: none;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
+    color: var(--body-quiet-color);
+    margin-top: 5px;
+    float: left;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin: 5px;
+    position: absolute;
+    right: 0;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
+    background-color: var(--darkened-bg);
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
+    color: var(--body-quiet-color);
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px;
+}
+
+.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
+    color: var(--body-fg);
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
+    float: right;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+    margin-left: 5px;
+    margin-right: auto;
+}
+
+.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+    margin-left: 2px;
+    margin-right: auto;
+}
+
+.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
+    border: solid var(--body-quiet-color) 1px;
+    outline: 0;
+}
+
+.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
+    background-color: var(--darkened-bg);
+    cursor: default;
+}
+
+.select2-container--admin-autocomplete.select2-container--disabled .select2-selection__choice__remove {
+    display: none;
+}
+
+.select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--multiple {
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+}
+
+.select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--multiple {
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+
+.select2-container--admin-autocomplete .select2-search--dropdown {
+    background: var(--darkened-bg);
+}
+
+.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
+    background: var(--body-bg);
+    color: var(--body-fg);
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+}
+
+.select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
+    background: transparent;
+    color: var(--body-fg);
+    border: none;
+    outline: 0;
+    box-shadow: none;
+    -webkit-appearance: textfield;
+}
+
+.select2-container--admin-autocomplete .select2-results > .select2-results__options {
+    max-height: 200px;
+    overflow-y: auto;
+    color: var(--body-fg);
+    background: var(--body-bg);
+}
+
+.select2-container--admin-autocomplete .select2-results__option[role=group] {
+    padding: 0;
+}
+
+.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
+    color: var(--body-quiet-color);
+}
+
+.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
+    background-color: var(--selected-bg);
+    color: var(--body-fg);
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option {
+    padding-left: 1em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__group {
+    padding-left: 0;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -1em;
+    padding-left: 2em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -2em;
+    padding-left: 3em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -3em;
+    padding-left: 4em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -4em;
+    padding-left: 5em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -5em;
+    padding-left: 6em;
+}
+
+.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
+    background-color: var(--primary);
+    color: var(--primary-fg);
+}
+
+.select2-container--admin-autocomplete .select2-results__group {
+    cursor: default;
+    display: block;
+    padding: 6px;
+}

File diff suppressed because it is too large
+ 1089 - 0
static_new/admin/css/base.css


+ 325 - 0
static_new/admin/css/changelists.css

@@ -0,0 +1,325 @@
+/* CHANGELISTS */
+
+#changelist {
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+}
+
+#changelist .changelist-form-container {
+    flex: 1 1 auto;
+    min-width: 0;
+}
+
+#changelist table {
+    width: 100%;
+}
+
+.change-list .hiddenfields { display:none; }
+
+.change-list .filtered table {
+    border-right: none;
+}
+
+.change-list .filtered {
+    min-height: 400px;
+}
+
+.change-list .filtered .results, .change-list .filtered .paginator,
+.filtered #toolbar, .filtered div.xfull {
+    width: auto;
+}
+
+.change-list .filtered table tbody th {
+    padding-right: 1em;
+}
+
+#changelist-form .results {
+    overflow-x: auto;
+    width: 100%;
+}
+
+#changelist .toplinks {
+    border-bottom: 1px solid var(--hairline-color);
+}
+
+#changelist .paginator {
+    color: var(--body-quiet-color);
+    border-bottom: 1px solid var(--hairline-color);
+    background: var(--body-bg);
+    overflow: hidden;
+}
+
+/* CHANGELIST TABLES */
+
+#changelist table thead th {
+    padding: 0;
+    white-space: nowrap;
+    vertical-align: middle;
+}
+
+#changelist table thead th.action-checkbox-column {
+    width: 1.5em;
+    text-align: center;
+}
+
+#changelist table tbody td.action-checkbox {
+    text-align: center;
+}
+
+#changelist table tfoot {
+    color: var(--body-quiet-color);
+}
+
+/* TOOLBAR */
+
+#toolbar {
+    padding: 8px 10px;
+    margin-bottom: 15px;
+    border-top: 1px solid var(--hairline-color);
+    border-bottom: 1px solid var(--hairline-color);
+    background: var(--darkened-bg);
+    color: var(--body-quiet-color);
+}
+
+#toolbar form input {
+    border-radius: 4px;
+    font-size: 0.875rem;
+    padding: 5px;
+    color: var(--body-fg);
+}
+
+#toolbar #searchbar {
+    height: 19px;
+    border: 1px solid var(--border-color);
+    padding: 2px 5px;
+    margin: 0;
+    vertical-align: top;
+    font-size: 0.8125rem;
+    max-width: 100%;
+}
+
+#toolbar #searchbar:focus {
+    border-color: var(--body-quiet-color);
+}
+
+#toolbar form input[type="submit"] {
+    border: 1px solid var(--border-color);
+    font-size: 0.8125rem;
+    padding: 4px 8px;
+    margin: 0;
+    vertical-align: middle;
+    background: var(--body-bg);
+    box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
+    cursor: pointer;
+    color: var(--body-fg);
+}
+
+#toolbar form input[type="submit"]:focus,
+#toolbar form input[type="submit"]:hover {
+    border-color: var(--body-quiet-color);
+}
+
+#changelist-search img {
+    vertical-align: middle;
+    margin-right: 4px;
+}
+
+#changelist-search .help {
+    word-break: break-word;
+}
+
+/* FILTER COLUMN */
+
+#changelist-filter {
+    flex: 0 0 240px;
+    order: 1;
+    background: var(--darkened-bg);
+    border-left: none;
+    margin: 0 0 0 30px;
+}
+
+#changelist-filter h2 {
+    font-size: 0.875rem;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    padding: 5px 15px;
+    margin-bottom: 12px;
+    border-bottom: none;
+}
+
+#changelist-filter h3,
+#changelist-filter details summary {
+    font-weight: 400;
+    padding: 0 15px;
+    margin-bottom: 10px;
+}
+
+#changelist-filter details summary > * {
+    display: inline;
+}
+
+#changelist-filter details > summary {
+    list-style-type: none;
+}
+
+#changelist-filter details > summary::-webkit-details-marker {
+    display: none;
+}
+
+#changelist-filter details > summary::before {
+    content: '→';
+    font-weight: bold;
+    color: var(--link-hover-color);
+}
+
+#changelist-filter details[open] > summary::before {
+    content: '↓';
+}
+
+#changelist-filter ul {
+    margin: 5px 0;
+    padding: 0 15px 15px;
+    border-bottom: 1px solid var(--hairline-color);
+}
+
+#changelist-filter ul:last-child {
+    border-bottom: none;
+}
+
+#changelist-filter li {
+    list-style-type: none;
+    margin-left: 0;
+    padding-left: 0;
+}
+
+#changelist-filter a {
+    display: block;
+    color: var(--body-quiet-color);
+    word-break: break-word;
+}
+
+#changelist-filter li.selected {
+    border-left: 5px solid var(--hairline-color);
+    padding-left: 10px;
+    margin-left: -15px;
+}
+
+#changelist-filter li.selected a {
+    color: var(--link-selected-fg);
+}
+
+#changelist-filter a:focus, #changelist-filter a:hover,
+#changelist-filter li.selected a:focus,
+#changelist-filter li.selected a:hover {
+    color: var(--link-hover-color);
+}
+
+#changelist-filter #changelist-filter-clear a {
+    font-size: 0.8125rem;
+    padding-bottom: 10px;
+    border-bottom: 1px solid var(--hairline-color);
+}
+
+/* DATE DRILLDOWN */
+
+.change-list ul.toplinks {
+    display: block;
+    float: left;
+    padding: 0;
+    margin: 0;
+    width: 100%;
+}
+
+.change-list ul.toplinks li {
+    padding: 3px 6px;
+    font-weight: bold;
+    list-style-type: none;
+    display: inline-block;
+}
+
+.change-list ul.toplinks .date-back a {
+    color: var(--body-quiet-color);
+}
+
+.change-list ul.toplinks .date-back a:focus,
+.change-list ul.toplinks .date-back a:hover {
+    color: var(--link-hover-color);
+}
+
+/* ACTIONS */
+
+.filtered .actions {
+    border-right: none;
+}
+
+#changelist table input {
+    margin: 0;
+    vertical-align: baseline;
+}
+
+#changelist table tbody tr.selected {
+    background-color: var(--selected-row);
+}
+
+#changelist .actions {
+    padding: 10px;
+    background: var(--body-bg);
+    border-top: none;
+    border-bottom: none;
+    line-height: 24px;
+    color: var(--body-quiet-color);
+    width: 100%;
+}
+
+#changelist .actions span.all,
+#changelist .actions span.action-counter,
+#changelist .actions span.clear,
+#changelist .actions span.question {
+    font-size: 0.8125rem;
+    margin: 0 0.5em;
+}
+
+#changelist .actions:last-child {
+    border-bottom: none;
+}
+
+#changelist .actions select {
+    vertical-align: top;
+    height: 24px;
+    color: var(--body-fg);
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    font-size: 0.875rem;
+    padding: 0 0 0 4px;
+    margin: 0;
+    margin-left: 10px;
+}
+
+#changelist .actions select:focus {
+    border-color: var(--body-quiet-color);
+}
+
+#changelist .actions label {
+    display: inline-block;
+    vertical-align: middle;
+    font-size: 0.8125rem;
+}
+
+#changelist .actions .button {
+    font-size: 0.8125rem;
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    background: var(--body-bg);
+    box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
+    cursor: pointer;
+    height: 24px;
+    line-height: 1;
+    padding: 4px 8px;
+    margin: 0;
+    color: var(--body-fg);
+}
+
+#changelist .actions .button:focus, #changelist .actions .button:hover {
+    border-color: var(--body-quiet-color);
+}

+ 33 - 0
static_new/admin/css/dark_mode.css

@@ -0,0 +1,33 @@
+@media (prefers-color-scheme: dark) {
+    :root {
+      --primary: #264b5d;
+      --primary-fg: #f7f7f7;
+  
+      --body-fg: #eeeeee;
+      --body-bg: #121212;
+      --body-quiet-color: #e0e0e0;
+      --body-loud-color: #ffffff;
+  
+      --breadcrumbs-link-fg: #e0e0e0;
+      --breadcrumbs-bg: var(--primary);
+  
+      --link-fg: #81d4fa;
+      --link-hover-color: #4ac1f7;
+      --link-selected-fg: #6f94c6;
+  
+      --hairline-color: #272727;
+      --border-color: #353535;
+  
+      --error-fg: #e35f5f;
+      --message-success-bg: #006b1b;
+      --message-warning-bg: #583305;
+      --message-error-bg: #570808;
+  
+      --darkened-bg: #212121;
+      --selected-bg: #1b1b1b;
+      --selected-row: #00363a;
+  
+      --close-button-bg: #333333;
+      --close-button-hover-bg: #666666;
+    }
+  }

+ 26 - 0
static_new/admin/css/dashboard.css

@@ -0,0 +1,26 @@
+/* DASHBOARD */
+
+.dashboard .module table th {
+    width: 100%;
+}
+
+.dashboard .module table td {
+    white-space: nowrap;
+}
+
+.dashboard .module table td a {
+    display: block;
+    padding-right: .6em;
+}
+
+/* RECENT ACTIONS MODULE */
+
+.module ul.actionlist {
+    margin-left: 0;
+}
+
+ul.actionlist li {
+    list-style-type: none;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}

+ 20 - 0
static_new/admin/css/fonts.css

@@ -0,0 +1,20 @@
+@font-face {
+    font-family: 'Roboto';
+    src: url('../fonts/Roboto-Bold-webfont.woff');
+    font-weight: 700;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'Roboto';
+    src: url('../fonts/Roboto-Regular-webfont.woff');
+    font-weight: 400;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'Roboto';
+    src: url('../fonts/Roboto-Light-webfont.woff');
+    font-weight: 300;
+    font-style: normal;
+}

+ 528 - 0
static_new/admin/css/forms.css

@@ -0,0 +1,528 @@
+@import url('widgets.css');
+
+/* FORM ROWS */
+
+.form-row {
+    overflow: hidden;
+    padding: 10px;
+    font-size: 0.8125rem;
+    border-bottom: 1px solid var(--hairline-color);
+}
+
+.form-row img, .form-row input {
+    vertical-align: middle;
+}
+
+.form-row label input[type="checkbox"] {
+    margin-top: 0;
+    vertical-align: 0;
+}
+
+form .form-row p {
+    padding-left: 0;
+}
+
+/* FORM LABELS */
+
+label {
+    font-weight: normal;
+    color: var(--body-quiet-color);
+    font-size: 0.8125rem;
+}
+
+.required label, label.required {
+    font-weight: bold;
+    color: var(--body-fg);
+}
+
+/* RADIO BUTTONS */
+
+form div.radiolist div {
+    padding-right: 7px;
+}
+
+form div.radiolist.inline div {
+    display: inline-block;
+}
+
+form div.radiolist label {
+    width: auto;
+}
+
+form div.radiolist input[type="radio"] {
+    margin: -2px 4px 0 0;
+    padding: 0;
+}
+
+form ul.inline {
+    margin-left: 0;
+    padding: 0;
+}
+
+form ul.inline li {
+    float: left;
+    padding-right: 7px;
+}
+
+/* ALIGNED FIELDSETS */
+
+.aligned label {
+    display: block;
+    padding: 4px 10px 0 0;
+    float: left;
+    width: 160px;
+    word-wrap: break-word;
+    line-height: 1;
+}
+
+.aligned label:not(.vCheckboxLabel):after {
+    content: '';
+    display: inline-block;
+    vertical-align: middle;
+    height: 26px;
+}
+
+.aligned label + p, .aligned label + div.help, .aligned label + div.readonly {
+    padding: 6px 0;
+    margin-top: 0;
+    margin-bottom: 0;
+    margin-left: 170px;
+    overflow-wrap: break-word;
+}
+
+.aligned ul label {
+    display: inline;
+    float: none;
+    width: auto;
+}
+
+.aligned .form-row input {
+    margin-bottom: 0;
+}
+
+.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField {
+    width: 350px;
+}
+
+form .aligned ul {
+    margin-left: 160px;
+    padding-left: 10px;
+}
+
+form .aligned div.radiolist {
+    display: inline-block;
+    margin: 0;
+    padding: 0;
+}
+
+form .aligned p.help,
+form .aligned div.help {
+    clear: left;
+    margin-top: 0;
+    margin-left: 160px;
+    padding-left: 10px;
+}
+
+form .aligned label + p.help,
+form .aligned label + div.help {
+    margin-left: 0;
+    padding-left: 0;
+}
+
+form .aligned p.help:last-child,
+form .aligned div.help:last-child {
+    margin-bottom: 0;
+    padding-bottom: 0;
+}
+
+form .aligned input + p.help,
+form .aligned textarea + p.help,
+form .aligned select + p.help,
+form .aligned input + div.help,
+form .aligned textarea + div.help,
+form .aligned select + div.help {
+    margin-left: 160px;
+    padding-left: 10px;
+}
+
+form .aligned ul li {
+    list-style: none;
+}
+
+form .aligned table p {
+    margin-left: 0;
+    padding-left: 0;
+}
+
+.aligned .vCheckboxLabel {
+    float: none;
+    width: auto;
+    display: inline-block;
+    vertical-align: -3px;
+    padding: 0 0 5px 5px;
+}
+
+.aligned .vCheckboxLabel + p.help,
+.aligned .vCheckboxLabel + div.help {
+    margin-top: -4px;
+}
+
+.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField {
+    width: 610px;
+}
+
+.checkbox-row p.help,
+.checkbox-row div.help {
+    margin-left: 0;
+    padding-left: 0;
+}
+
+fieldset .fieldBox {
+    float: left;
+    margin-right: 20px;
+}
+
+/* WIDE FIELDSETS */
+
+.wide label {
+    width: 200px;
+}
+
+form .wide p,
+form .wide input + p.help,
+form .wide input + div.help {
+    margin-left: 200px;
+}
+
+form .wide p.help,
+form .wide div.help {
+    padding-left: 38px;
+}
+
+form div.help ul {
+    padding-left: 0;
+    margin-left: 0;
+}
+
+.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField {
+    width: 450px;
+}
+
+/* COLLAPSED FIELDSETS */
+
+fieldset.collapsed * {
+    display: none;
+}
+
+fieldset.collapsed h2, fieldset.collapsed {
+    display: block;
+}
+
+fieldset.collapsed {
+    border: 1px solid var(--hairline-color);
+    border-radius: 4px;
+    overflow: hidden;
+}
+
+fieldset.collapsed h2 {
+    background: var(--darkened-bg);
+    color: var(--body-quiet-color);
+}
+
+fieldset .collapse-toggle {
+    color: var(--header-link-color);
+}
+
+fieldset.collapsed .collapse-toggle {
+    background: transparent;
+    display: inline;
+    color: var(--link-fg);
+}
+
+/* MONOSPACE TEXTAREAS */
+
+fieldset.monospace textarea {
+    font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+}
+
+/* SUBMIT ROW */
+
+.submit-row {
+    padding: 12px 14px 7px;
+    margin: 0 0 20px;
+    background: var(--darkened-bg);
+    border: 1px solid var(--hairline-color);
+    border-radius: 4px;
+    text-align: right;
+    overflow: hidden;
+}
+
+body.popup .submit-row {
+    overflow: auto;
+}
+
+.submit-row input {
+    height: 35px;
+    line-height: 15px;
+    margin: 0 0 5px 5px;
+}
+
+.submit-row input.default {
+    margin: 0 0 5px 8px;
+    text-transform: uppercase;
+}
+
+.submit-row p {
+    margin: 0.3em;
+}
+
+.submit-row p.deletelink-box {
+    float: left;
+    margin: 0;
+}
+
+.submit-row a.deletelink {
+    display: block;
+    background: var(--delete-button-bg);
+    border-radius: 4px;
+    padding: 10px 15px;
+    height: 15px;
+    line-height: 15px;
+    margin-bottom: 5px;
+    color: var(--button-fg);
+}
+
+.submit-row a.closelink {
+    display: inline-block;
+    background: var(--close-button-bg);
+    border-radius: 4px;
+    padding: 10px 15px;
+    height: 15px;
+    line-height: 15px;
+    margin: 0 0 0 5px;
+    color: var(--button-fg);
+}
+
+.submit-row a.deletelink:focus,
+.submit-row a.deletelink:hover,
+.submit-row a.deletelink:active {
+    background: var(--delete-button-hover-bg);
+}
+
+.submit-row a.closelink:focus,
+.submit-row a.closelink:hover,
+.submit-row a.closelink:active {
+    background: var(--close-button-hover-bg);
+}
+
+/* CUSTOM FORM FIELDS */
+
+.vSelectMultipleField {
+    vertical-align: top;
+}
+
+.vCheckboxField {
+    border: none;
+}
+
+.vDateField, .vTimeField {
+    margin-right: 2px;
+    margin-bottom: 4px;
+}
+
+.vDateField {
+    min-width: 6.85em;
+}
+
+.vTimeField {
+    min-width: 4.7em;
+}
+
+.vURLField {
+    width: 30em;
+}
+
+.vLargeTextField, .vXMLLargeTextField {
+    width: 48em;
+}
+
+.flatpages-flatpage #id_content {
+    height: 40.2em;
+}
+
+.module table .vPositiveSmallIntegerField {
+    width: 2.2em;
+}
+
+.vIntegerField {
+    width: 5em;
+}
+
+.vBigIntegerField {
+    width: 10em;
+}
+
+.vForeignKeyRawIdAdminField {
+    width: 5em;
+}
+
+.vTextField, .vUUIDField {
+    width: 20em;
+}
+
+/* INLINES */
+
+.inline-group {
+    padding: 0;
+    margin: 0 0 30px;
+}
+
+.inline-group thead th {
+    padding: 8px 10px;
+}
+
+.inline-group .aligned label {
+    width: 160px;
+}
+
+.inline-related {
+    position: relative;
+}
+
+.inline-related h3 {
+    margin: 0;
+    color: var(--body-quiet-color);
+    padding: 5px;
+    font-size: 0.8125rem;
+    background: var(--darkened-bg);
+    border-top: 1px solid var(--hairline-color);
+    border-bottom: 1px solid var(--hairline-color);
+}
+
+.inline-related h3 span.delete {
+    float: right;
+}
+
+.inline-related h3 span.delete label {
+    margin-left: 2px;
+    font-size: 0.6875rem;
+}
+
+.inline-related fieldset {
+    margin: 0;
+    background: var(--body-bg);
+    border: none;
+    width: 100%;
+}
+
+.inline-related fieldset.module h3 {
+    margin: 0;
+    padding: 2px 5px 3px 5px;
+    font-size: 0.6875rem;
+    text-align: left;
+    font-weight: bold;
+    background: #bcd;
+    color: var(--body-bg);
+}
+
+.inline-group .tabular fieldset.module {
+    border: none;
+}
+
+.inline-related.tabular fieldset.module table {
+    width: 100%;
+    overflow-x: scroll;
+}
+
+.last-related fieldset {
+    border: none;
+}
+
+.inline-group .tabular tr.has_original td {
+    padding-top: 2em;
+}
+
+.inline-group .tabular tr td.original {
+    padding: 2px 0 0 0;
+    width: 0;
+    _position: relative;
+}
+
+.inline-group .tabular th.original {
+    width: 0px;
+    padding: 0;
+}
+
+.inline-group .tabular td.original p {
+    position: absolute;
+    left: 0;
+    height: 1.1em;
+    padding: 2px 9px;
+    overflow: hidden;
+    font-size: 0.5625rem;
+    font-weight: bold;
+    color: var(--body-quiet-color);
+    _width: 700px;
+}
+
+.inline-group ul.tools {
+    padding: 0;
+    margin: 0;
+    list-style: none;
+}
+
+.inline-group ul.tools li {
+    display: inline;
+    padding: 0 5px;
+}
+
+.inline-group div.add-row,
+.inline-group .tabular tr.add-row td {
+    color: var(--body-quiet-color);
+    background: var(--darkened-bg);
+    padding: 8px 10px;
+    border-bottom: 1px solid var(--hairline-color);
+}
+
+.inline-group .tabular tr.add-row td {
+    padding: 8px 10px;
+    border-bottom: 1px solid var(--hairline-color);
+}
+
+.inline-group ul.tools a.add,
+.inline-group div.add-row a,
+.inline-group .tabular tr.add-row td a {
+    background: url(../img/icon-addlink.svg) 0 1px no-repeat;
+    padding-left: 16px;
+    font-size: 0.75rem;
+}
+
+.empty-form {
+    display: none;
+}
+
+/* RELATED FIELD ADD ONE / LOOKUP */
+
+.related-lookup {
+    margin-left: 5px;
+    display: inline-block;
+    vertical-align: middle;
+    background-repeat: no-repeat;
+    background-size: 14px;
+}
+
+.related-lookup {
+    width: 16px;
+    height: 16px;
+    background-image: url(../img/search.svg);
+}
+
+form .related-widget-wrapper ul {
+    display: inline-block;
+    margin-left: 0;
+    padding-left: 0;
+}
+
+.clearable-file-input input {
+    margin-top: 0;
+}

+ 61 - 0
static_new/admin/css/login.css

@@ -0,0 +1,61 @@
+/* LOGIN FORM */
+
+.login {
+    background: var(--darkened-bg);
+    height: auto;
+}
+
+.login #header {
+    height: auto;
+    padding: 15px 16px;
+    justify-content: center;
+}
+
+.login #header h1 {
+    font-size: 1.125rem;
+    margin: 0;
+}
+
+.login #header h1 a {
+    color: var(--header-link-color);
+}
+
+.login #content {
+    padding: 20px 20px 0;
+}
+
+.login #container {
+    background: var(--body-bg);
+    border: 1px solid var(--hairline-color);
+    border-radius: 4px;
+    overflow: hidden;
+    width: 28em;
+    min-width: 300px;
+    margin: 100px auto;
+    height: auto;
+}
+
+.login .form-row {
+    padding: 4px 0;
+}
+
+.login .form-row label {
+    display: block;
+    line-height: 2em;
+}
+
+.login .form-row #id_username, .login .form-row #id_password {
+    padding: 8px;
+    width: 100%;
+    box-sizing: border-box;
+}
+
+.login .submit-row {
+    padding: 1em 0 0 0;
+    margin: 0;
+    text-align: center;
+}
+
+.login .password-reset-link {
+    text-align: center;
+}

+ 139 - 0
static_new/admin/css/nav_sidebar.css

@@ -0,0 +1,139 @@
+.sticky {
+    position: sticky;
+    top: 0;
+    max-height: 100vh;
+}
+
+.toggle-nav-sidebar {
+    z-index: 20;
+    left: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex: 0 0 23px;
+    width: 23px;
+    border: 0;
+    border-right: 1px solid var(--hairline-color);
+    background-color: var(--body-bg);
+    cursor: pointer;
+    font-size: 1.25rem;
+    color: var(--link-fg);
+    padding: 0;
+}
+
+[dir="rtl"] .toggle-nav-sidebar {
+    border-left: 1px solid var(--hairline-color);
+    border-right: 0;
+}
+
+.toggle-nav-sidebar:hover,
+.toggle-nav-sidebar:focus {
+    background-color: var(--darkened-bg);
+}
+
+#nav-sidebar {
+    z-index: 15;
+    flex: 0 0 275px;
+    left: -276px;
+    margin-left: -276px;
+    border-top: 1px solid transparent;
+    border-right: 1px solid var(--hairline-color);
+    background-color: var(--body-bg);
+    overflow: auto;
+}
+
+[dir="rtl"] #nav-sidebar {
+    border-left: 1px solid var(--hairline-color);
+    border-right: 0;
+    left: 0;
+    margin-left: 0;
+    right: -276px;
+    margin-right: -276px;
+}
+
+.toggle-nav-sidebar::before {
+    content: '\00BB';
+}
+
+.main.shifted .toggle-nav-sidebar::before {
+    content: '\00AB';
+}
+
+.main.shifted > #nav-sidebar {
+    margin-left: 0;
+}
+
+[dir="rtl"] .main.shifted > #nav-sidebar {
+    margin-right: 0;
+}
+
+#nav-sidebar .module th {
+    width: 100%;
+    overflow-wrap: anywhere;
+}
+
+#nav-sidebar .module th,
+#nav-sidebar .module caption {
+    padding-left: 16px;
+}
+
+#nav-sidebar .module td {
+    white-space: nowrap;
+}
+
+[dir="rtl"] #nav-sidebar .module th,
+[dir="rtl"] #nav-sidebar .module caption {
+    padding-left: 8px;
+    padding-right: 16px;
+}
+
+#nav-sidebar .current-app .section:link,
+#nav-sidebar .current-app .section:visited {
+    color: var(--header-color);
+    font-weight: bold;
+}
+
+#nav-sidebar .current-model {
+    background: var(--selected-row);
+}
+
+.main > #nav-sidebar + .content {
+    max-width: calc(100% - 23px);
+}
+
+.main.shifted > #nav-sidebar + .content {
+    max-width: calc(100% - 299px);
+}
+
+@media (max-width: 767px) {
+    #nav-sidebar, #toggle-nav-sidebar {
+        display: none;
+    }
+
+    .main > #nav-sidebar + .content,
+    .main.shifted > #nav-sidebar + .content {
+        max-width: 100%;
+    }
+}
+
+#nav-filter {
+    width: 100%;
+    box-sizing: border-box;
+    padding: 2px 5px;
+    margin: 5px 0;
+    border: 1px solid var(--border-color);
+    background-color: var(--darkened-bg);
+    color: var(--body-fg);
+}
+
+#nav-filter:focus {
+    border-color: var(--body-quiet-color);
+}
+
+#nav-filter.no-results {
+    background: var(--message-error-bg);
+}
+
+#nav-sidebar table {
+    width: 100%;
+}

File diff suppressed because it is too large
+ 1015 - 0
static_new/admin/css/responsive.css


+ 80 - 0
static_new/admin/css/responsive_rtl.css

@@ -0,0 +1,80 @@
+/* TABLETS */
+
+@media (max-width: 1024px) {
+    [dir="rtl"] .colMS {
+        margin-right: 0;
+    }
+
+    [dir="rtl"] #user-tools {
+        text-align: right;
+    }
+
+    [dir="rtl"] #changelist .actions label {
+        padding-left: 10px;
+        padding-right: 0;
+    }
+
+    [dir="rtl"] #changelist .actions select {
+        margin-left: 0;
+        margin-right: 15px;
+    }
+
+    [dir="rtl"] .change-list .filtered .results,
+    [dir="rtl"] .change-list .filtered .paginator,
+    [dir="rtl"] .filtered #toolbar,
+    [dir="rtl"] .filtered div.xfull,
+    [dir="rtl"] .filtered .actions,
+    [dir="rtl"] #changelist-filter {
+        margin-left: 0;
+    }
+
+    [dir="rtl"] .inline-group ul.tools a.add,
+    [dir="rtl"] .inline-group div.add-row a,
+    [dir="rtl"] .inline-group .tabular tr.add-row td a {
+        padding: 8px 26px 8px 10px;
+        background-position: calc(100% - 8px) 9px;
+    }
+
+    [dir="rtl"] .related-widget-wrapper-link + .selector {
+        margin-right: 0;
+        margin-left: 15px;
+    }
+
+    [dir="rtl"] .selector .selector-filter label {
+        margin-right: 0;
+        margin-left: 8px;
+    }
+
+    [dir="rtl"] .object-tools li {
+        float: right;
+    }
+
+    [dir="rtl"] .object-tools li + li {
+        margin-left: 0;
+        margin-right: 15px;
+    }
+
+    [dir="rtl"] .dashboard .module table td a {
+        padding-left: 0;
+        padding-right: 16px;
+    }
+}
+
+/* MOBILE */
+
+@media (max-width: 767px) {
+    [dir="rtl"] .aligned .related-lookup,
+    [dir="rtl"] .aligned .datetimeshortcuts {
+        margin-left: 0;
+        margin-right: 15px;
+    }
+
+    [dir="rtl"] .aligned ul {
+        margin-right: 0;
+    }
+
+    [dir="rtl"] #changelist-filter {
+        margin-left: 0;
+        margin-right: 0;
+    }
+}

+ 239 - 0
static_new/admin/css/rtl.css

@@ -0,0 +1,239 @@
+/* GLOBAL */
+
+th {
+    text-align: right;
+}
+
+.module h2, .module caption {
+    text-align: right;
+}
+
+.module ul, .module ol {
+    margin-left: 0;
+    margin-right: 1.5em;
+}
+
+.viewlink, .addlink, .changelink {
+    padding-left: 0;
+    padding-right: 16px;
+    background-position: 100% 1px;
+}
+
+.deletelink {
+    padding-left: 0;
+    padding-right: 16px;
+    background-position: 100% 1px;
+}
+
+.object-tools {
+    float: left;
+}
+
+thead th:first-child,
+tfoot td:first-child {
+    border-left: none;
+}
+
+/* LAYOUT */
+
+#user-tools {
+    right: auto;
+    left: 0;
+    text-align: left;
+}
+
+div.breadcrumbs {
+    text-align: right;
+}
+
+#content-main {
+    float: right;
+}
+
+#content-related {
+    float: left;
+    margin-left: -300px;
+    margin-right: auto;
+}
+
+.colMS {
+    margin-left: 300px;
+    margin-right: 0;
+}
+
+/* SORTABLE TABLES */
+
+table thead th.sorted .sortoptions {
+   float: left;
+}
+
+thead th.sorted .text {
+    padding-right: 0;
+    padding-left: 42px;
+}
+
+/* dashboard styles */
+
+.dashboard .module table td a {
+    padding-left: .6em;
+    padding-right: 16px;
+}
+
+/* changelists styles */
+
+.change-list .filtered table {
+    border-left: none;
+    border-right: 0px none;
+}
+
+#changelist-filter {
+    border-left: none;
+    border-right: none;
+    margin-left: 0;
+    margin-right: 30px;
+}
+
+#changelist-filter li.selected {
+    border-left: none;
+    padding-left: 10px;
+    margin-left: 0;
+    border-right: 5px solid var(--hairline-color);
+    padding-right: 10px;
+    margin-right: -15px;
+}
+
+#changelist table tbody td:first-child, #changelist table tbody th:first-child {
+    border-right: none;
+    border-left: none;
+}
+
+/* FORMS */
+
+.aligned label {
+    padding: 0 0 3px 1em;
+    float: right;
+}
+
+.submit-row {
+    text-align: left
+}
+
+.submit-row p.deletelink-box {
+    float: right;
+}
+
+.submit-row input.default {
+    margin-left: 0;
+}
+
+.vDateField, .vTimeField {
+    margin-left: 2px;
+}
+
+.aligned .form-row input {
+    margin-left: 5px;
+}
+
+form .aligned p.help, form .aligned div.help {
+    clear: right;
+}
+
+form .aligned ul {
+    margin-right: 163px;
+    margin-left: 0;
+}
+
+form ul.inline li {
+    float: right;
+    padding-right: 0;
+    padding-left: 7px;
+}
+
+input[type=submit].default, .submit-row input.default {
+    float: left;
+}
+
+fieldset .fieldBox {
+    float: right;
+    margin-left: 20px;
+    margin-right: 0;
+}
+
+.errorlist li {
+    background-position: 100% 12px;
+    padding: 0;
+}
+
+.errornote {
+    background-position: 100% 12px;
+    padding: 10px 12px;
+}
+
+/* WIDGETS */
+
+.calendarnav-previous {
+    top: 0;
+    left: auto;
+    right: 10px;
+    background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
+}
+
+.calendarbox .calendarnav-previous:focus,
+.calendarbox .calendarnav-previous:hover {
+    background-position: 0 -45px;
+}
+
+.calendarnav-next {
+    top: 0;
+    right: auto;
+    left: 10px;
+    background: url(../img/calendar-icons.svg) 0 0 no-repeat;
+}
+
+.calendarbox .calendarnav-next:focus,
+.calendarbox .calendarnav-next:hover {
+    background-position: 0 -15px;
+}
+
+.calendar caption, .calendarbox h2 {
+    text-align: center;
+}
+
+.selector {
+    float: right;
+}
+
+.selector .selector-filter {
+    text-align: right;
+}
+
+.inline-deletelink {
+    float: left;
+}
+
+form .form-row p.datetime {
+    overflow: hidden;
+}
+
+.related-widget-wrapper {
+    float: right;
+}
+
+/* MISC */
+
+.inline-related h2, .inline-group h2 {
+    text-align: right
+}
+
+.inline-related h3 span.delete {
+    padding-right: 20px;
+    padding-left: inherit;
+    left: 10px;
+    right: inherit;
+    float:left;
+}
+
+.inline-related h3 span.delete label {
+    margin-left: inherit;
+    margin-right: 2px;
+}

+ 21 - 0
static_new/admin/css/vendor/select2/LICENSE-SELECT2.md

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 481 - 0
static_new/admin/css/vendor/select2/select2.css

@@ -0,0 +1,481 @@
+.select2-container {
+  box-sizing: border-box;
+  display: inline-block;
+  margin: 0;
+  position: relative;
+  vertical-align: middle; }
+  .select2-container .select2-selection--single {
+    box-sizing: border-box;
+    cursor: pointer;
+    display: block;
+    height: 28px;
+    user-select: none;
+    -webkit-user-select: none; }
+    .select2-container .select2-selection--single .select2-selection__rendered {
+      display: block;
+      padding-left: 8px;
+      padding-right: 20px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap; }
+    .select2-container .select2-selection--single .select2-selection__clear {
+      position: relative; }
+  .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
+    padding-right: 8px;
+    padding-left: 20px; }
+  .select2-container .select2-selection--multiple {
+    box-sizing: border-box;
+    cursor: pointer;
+    display: block;
+    min-height: 32px;
+    user-select: none;
+    -webkit-user-select: none; }
+    .select2-container .select2-selection--multiple .select2-selection__rendered {
+      display: inline-block;
+      overflow: hidden;
+      padding-left: 8px;
+      text-overflow: ellipsis;
+      white-space: nowrap; }
+  .select2-container .select2-search--inline {
+    float: left; }
+    .select2-container .select2-search--inline .select2-search__field {
+      box-sizing: border-box;
+      border: none;
+      font-size: 100%;
+      margin-top: 5px;
+      padding: 0; }
+      .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
+        -webkit-appearance: none; }
+
+.select2-dropdown {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  box-sizing: border-box;
+  display: block;
+  position: absolute;
+  left: -100000px;
+  width: 100%;
+  z-index: 1051; }
+
+.select2-results {
+  display: block; }
+
+.select2-results__options {
+  list-style: none;
+  margin: 0;
+  padding: 0; }
+
+.select2-results__option {
+  padding: 6px;
+  user-select: none;
+  -webkit-user-select: none; }
+  .select2-results__option[aria-selected] {
+    cursor: pointer; }
+
+.select2-container--open .select2-dropdown {
+  left: 0; }
+
+.select2-container--open .select2-dropdown--above {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--open .select2-dropdown--below {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-search--dropdown {
+  display: block;
+  padding: 4px; }
+  .select2-search--dropdown .select2-search__field {
+    padding: 4px;
+    width: 100%;
+    box-sizing: border-box; }
+    .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
+      -webkit-appearance: none; }
+  .select2-search--dropdown.select2-search--hide {
+    display: none; }
+
+.select2-close-mask {
+  border: 0;
+  margin: 0;
+  padding: 0;
+  display: block;
+  position: fixed;
+  left: 0;
+  top: 0;
+  min-height: 100%;
+  min-width: 100%;
+  height: auto;
+  width: auto;
+  opacity: 0;
+  z-index: 99;
+  background-color: #fff;
+  filter: alpha(opacity=0); }
+
+.select2-hidden-accessible {
+  border: 0 !important;
+  clip: rect(0 0 0 0) !important;
+  -webkit-clip-path: inset(50%) !important;
+  clip-path: inset(50%) !important;
+  height: 1px !important;
+  overflow: hidden !important;
+  padding: 0 !important;
+  position: absolute !important;
+  width: 1px !important;
+  white-space: nowrap !important; }
+
+.select2-container--default .select2-selection--single {
+  background-color: #fff;
+  border: 1px solid #aaa;
+  border-radius: 4px; }
+  .select2-container--default .select2-selection--single .select2-selection__rendered {
+    color: #444;
+    line-height: 28px; }
+  .select2-container--default .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold; }
+  .select2-container--default .select2-selection--single .select2-selection__placeholder {
+    color: #999; }
+  .select2-container--default .select2-selection--single .select2-selection__arrow {
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px; }
+    .select2-container--default .select2-selection--single .select2-selection__arrow b {
+      border-color: #888 transparent transparent transparent;
+      border-style: solid;
+      border-width: 5px 4px 0 4px;
+      height: 0;
+      left: 50%;
+      margin-left: -4px;
+      margin-top: -2px;
+      position: absolute;
+      top: 50%;
+      width: 0; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
+  float: left; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+  left: 1px;
+  right: auto; }
+
+.select2-container--default.select2-container--disabled .select2-selection--single {
+  background-color: #eee;
+  cursor: default; }
+  .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
+    display: none; }
+
+.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
+  border-color: transparent transparent #888 transparent;
+  border-width: 0 4px 5px 4px; }
+
+.select2-container--default .select2-selection--multiple {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  cursor: text; }
+  .select2-container--default .select2-selection--multiple .select2-selection__rendered {
+    box-sizing: border-box;
+    list-style: none;
+    margin: 0;
+    padding: 0 5px;
+    width: 100%; }
+    .select2-container--default .select2-selection--multiple .select2-selection__rendered li {
+      list-style: none; }
+  .select2-container--default .select2-selection--multiple .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin-top: 5px;
+    margin-right: 10px;
+    padding: 1px; }
+  .select2-container--default .select2-selection--multiple .select2-selection__choice {
+    background-color: #e4e4e4;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px; }
+  .select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
+    color: #999;
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px; }
+    .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
+      color: #333; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
+  float: right; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  margin-left: 5px;
+  margin-right: auto; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+  margin-left: 2px;
+  margin-right: auto; }
+
+.select2-container--default.select2-container--focus .select2-selection--multiple {
+  border: solid black 1px;
+  outline: 0; }
+
+.select2-container--default.select2-container--disabled .select2-selection--multiple {
+  background-color: #eee;
+  cursor: default; }
+
+.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
+  display: none; }
+
+.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--default .select2-search--dropdown .select2-search__field {
+  border: 1px solid #aaa; }
+
+.select2-container--default .select2-search--inline .select2-search__field {
+  background: transparent;
+  border: none;
+  outline: 0;
+  box-shadow: none;
+  -webkit-appearance: textfield; }
+
+.select2-container--default .select2-results > .select2-results__options {
+  max-height: 200px;
+  overflow-y: auto; }
+
+.select2-container--default .select2-results__option[role=group] {
+  padding: 0; }
+
+.select2-container--default .select2-results__option[aria-disabled=true] {
+  color: #999; }
+
+.select2-container--default .select2-results__option[aria-selected=true] {
+  background-color: #ddd; }
+
+.select2-container--default .select2-results__option .select2-results__option {
+  padding-left: 1em; }
+  .select2-container--default .select2-results__option .select2-results__option .select2-results__group {
+    padding-left: 0; }
+  .select2-container--default .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -1em;
+    padding-left: 2em; }
+    .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+      margin-left: -2em;
+      padding-left: 3em; }
+      .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+        margin-left: -3em;
+        padding-left: 4em; }
+        .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+          margin-left: -4em;
+          padding-left: 5em; }
+          .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+            margin-left: -5em;
+            padding-left: 6em; }
+
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+  background-color: #5897fb;
+  color: white; }
+
+.select2-container--default .select2-results__group {
+  cursor: default;
+  display: block;
+  padding: 6px; }
+
+.select2-container--classic .select2-selection--single {
+  background-color: #f7f7f7;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  outline: 0;
+  background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
+  background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
+  background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+  .select2-container--classic .select2-selection--single:focus {
+    border: 1px solid #5897fb; }
+  .select2-container--classic .select2-selection--single .select2-selection__rendered {
+    color: #444;
+    line-height: 28px; }
+  .select2-container--classic .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin-right: 10px; }
+  .select2-container--classic .select2-selection--single .select2-selection__placeholder {
+    color: #999; }
+  .select2-container--classic .select2-selection--single .select2-selection__arrow {
+    background-color: #ddd;
+    border: none;
+    border-left: 1px solid #aaa;
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px;
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px;
+    background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+    background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+    background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
+    background-repeat: repeat-x;
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
+    .select2-container--classic .select2-selection--single .select2-selection__arrow b {
+      border-color: #888 transparent transparent transparent;
+      border-style: solid;
+      border-width: 5px 4px 0 4px;
+      height: 0;
+      left: 50%;
+      margin-left: -4px;
+      margin-top: -2px;
+      position: absolute;
+      top: 50%;
+      width: 0; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
+  float: left; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+  border: none;
+  border-right: 1px solid #aaa;
+  border-radius: 0;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+  left: 1px;
+  right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--single {
+  border: 1px solid #5897fb; }
+  .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
+    background: transparent;
+    border: none; }
+    .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
+      border-color: transparent transparent #888 transparent;
+      border-width: 0 4px 5px 4px; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+  background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
+  background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
+  background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
+  background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
+  background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
+
+.select2-container--classic .select2-selection--multiple {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  cursor: text;
+  outline: 0; }
+  .select2-container--classic .select2-selection--multiple:focus {
+    border: 1px solid #5897fb; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__rendered {
+    list-style: none;
+    margin: 0;
+    padding: 0 5px; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__clear {
+    display: none; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__choice {
+    background-color: #e4e4e4;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
+    color: #888;
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px; }
+    .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
+      color: #555; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  float: right;
+  margin-left: 5px;
+  margin-right: auto; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+  margin-left: 2px;
+  margin-right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--multiple {
+  border: 1px solid #5897fb; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--classic .select2-search--dropdown .select2-search__field {
+  border: 1px solid #aaa;
+  outline: 0; }
+
+.select2-container--classic .select2-search--inline .select2-search__field {
+  outline: 0;
+  box-shadow: none; }
+
+.select2-container--classic .select2-dropdown {
+  background-color: white;
+  border: 1px solid transparent; }
+
+.select2-container--classic .select2-dropdown--above {
+  border-bottom: none; }
+
+.select2-container--classic .select2-dropdown--below {
+  border-top: none; }
+
+.select2-container--classic .select2-results > .select2-results__options {
+  max-height: 200px;
+  overflow-y: auto; }
+
+.select2-container--classic .select2-results__option[role=group] {
+  padding: 0; }
+
+.select2-container--classic .select2-results__option[aria-disabled=true] {
+  color: grey; }
+
+.select2-container--classic .select2-results__option--highlighted[aria-selected] {
+  background-color: #3875d7;
+  color: white; }
+
+.select2-container--classic .select2-results__group {
+  cursor: default;
+  display: block;
+  padding: 6px; }
+
+.select2-container--classic.select2-container--open .select2-dropdown {
+  border-color: #5897fb; }

File diff suppressed because it is too large
+ 1 - 0
static_new/admin/css/vendor/select2/select2.min.css


+ 580 - 0
static_new/admin/css/widgets.css

@@ -0,0 +1,580 @@
+/* SELECTOR (FILTER INTERFACE) */
+
+.selector {
+    width: 800px;
+    float: left;
+    display: flex;
+}
+
+.selector select {
+    width: 380px;
+    height: 17.2em;
+    flex: 1 0 auto;
+}
+
+.selector-available, .selector-chosen {
+    width: 380px;
+    text-align: center;
+    margin-bottom: 5px;
+    display: flex;
+    flex-direction: column;
+}
+
+.selector-chosen select {
+    border-top: none;
+}
+
+.selector-available h2, .selector-chosen h2 {
+    border: 1px solid var(--border-color);
+    border-radius: 4px 4px 0 0;
+}
+
+.selector-chosen h2 {
+    background: var(--primary);
+    color: var(--header-link-color);
+}
+
+.selector .selector-available h2 {
+    background: var(--darkened-bg);
+    color: var(--body-quiet-color);
+}
+
+.selector .selector-filter {
+    border: 1px solid var(--border-color);
+    border-width: 0 1px;
+    padding: 8px;
+    color: var(--body-quiet-color);
+    font-size: 0.625rem;
+    margin: 0;
+    text-align: left;
+}
+
+.selector .selector-filter label,
+.inline-group .aligned .selector .selector-filter label {
+    float: left;
+    margin: 7px 0 0;
+    width: 18px;
+    height: 18px;
+    padding: 0;
+    overflow: hidden;
+    line-height: 1;
+}
+
+.selector .selector-available input {
+    width: 320px;
+    margin-left: 8px;
+}
+
+.selector ul.selector-chooser {
+    align-self: center;
+    width: 22px;
+    background-color: var(--selected-bg);
+    border-radius: 10px;
+    margin: 0 5px;
+    padding: 0;
+    transform: translateY(-17px);
+}
+
+.selector-chooser li {
+    margin: 0;
+    padding: 3px;
+    list-style-type: none;
+}
+
+.selector select {
+    padding: 0 10px;
+    margin: 0 0 10px;
+    border-radius: 0 0 4px 4px;
+}
+
+.selector-add, .selector-remove {
+    width: 16px;
+    height: 16px;
+    display: block;
+    text-indent: -3000px;
+    overflow: hidden;
+    cursor: default;
+    opacity: 0.55;
+}
+
+.active.selector-add, .active.selector-remove {
+    opacity: 1;
+}
+
+.active.selector-add:hover, .active.selector-remove:hover {
+    cursor: pointer;
+}
+
+.selector-add {
+    background: url(../img/selector-icons.svg) 0 -96px no-repeat;
+}
+
+.active.selector-add:focus, .active.selector-add:hover {
+    background-position: 0 -112px;
+}
+
+.selector-remove {
+    background: url(../img/selector-icons.svg) 0 -64px no-repeat;
+}
+
+.active.selector-remove:focus, .active.selector-remove:hover {
+    background-position: 0 -80px;
+}
+
+a.selector-chooseall, a.selector-clearall {
+    display: inline-block;
+    height: 16px;
+    text-align: left;
+    margin: 1px auto 3px;
+    overflow: hidden;
+    font-weight: bold;
+    line-height: 16px;
+    color: var(--body-quiet-color);
+    text-decoration: none;
+    opacity: 0.55;
+}
+
+a.active.selector-chooseall:focus, a.active.selector-clearall:focus,
+a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
+    color: var(--link-fg);
+}
+
+a.active.selector-chooseall, a.active.selector-clearall {
+    opacity: 1;
+}
+
+a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
+    cursor: pointer;
+}
+
+a.selector-chooseall {
+    padding: 0 18px 0 0;
+    background: url(../img/selector-icons.svg) right -160px no-repeat;
+    cursor: default;
+}
+
+a.active.selector-chooseall:focus, a.active.selector-chooseall:hover {
+    background-position: 100% -176px;
+}
+
+a.selector-clearall {
+    padding: 0 0 0 18px;
+    background: url(../img/selector-icons.svg) 0 -128px no-repeat;
+    cursor: default;
+}
+
+a.active.selector-clearall:focus, a.active.selector-clearall:hover {
+    background-position: 0 -144px;
+}
+
+/* STACKED SELECTORS */
+
+.stacked {
+    float: left;
+    width: 490px;
+    display: block;
+}
+
+.stacked select {
+    width: 480px;
+    height: 10.1em;
+}
+
+.stacked .selector-available, .stacked .selector-chosen {
+    width: 480px;
+}
+
+.stacked .selector-available {
+    margin-bottom: 0;
+}
+
+.stacked .selector-available input {
+    width: 422px;
+}
+
+.stacked ul.selector-chooser {
+    height: 22px;
+    width: 50px;
+    margin: 0 0 10px 40%;
+    background-color: #eee;
+    border-radius: 10px;
+    transform: none;
+}
+
+.stacked .selector-chooser li {
+    float: left;
+    padding: 3px 3px 3px 5px;
+}
+
+.stacked .selector-chooseall, .stacked .selector-clearall {
+    display: none;
+}
+
+.stacked .selector-add {
+    background: url(../img/selector-icons.svg) 0 -32px no-repeat;
+    cursor: default;
+}
+
+.stacked .active.selector-add {
+    background-position: 0 -32px;
+    cursor: pointer;
+}
+
+.stacked .active.selector-add:focus, .stacked .active.selector-add:hover {
+    background-position: 0 -48px;
+    cursor: pointer;
+}
+
+.stacked .selector-remove {
+    background: url(../img/selector-icons.svg) 0 0 no-repeat;
+    cursor: default;
+}
+
+.stacked .active.selector-remove {
+    background-position: 0 0px;
+    cursor: pointer;
+}
+
+.stacked .active.selector-remove:focus, .stacked .active.selector-remove:hover {
+    background-position: 0 -16px;
+    cursor: pointer;
+}
+
+.selector .help-icon {
+    background: url(../img/icon-unknown.svg) 0 0 no-repeat;
+    display: inline-block;
+    vertical-align: middle;
+    margin: -2px 0 0 2px;
+    width: 13px;
+    height: 13px;
+}
+
+.selector .selector-chosen .help-icon {
+    background: url(../img/icon-unknown-alt.svg) 0 0 no-repeat;
+}
+
+.selector .search-label-icon {
+    background: url(../img/search.svg) 0 0 no-repeat;
+    display: inline-block;
+    height: 18px;
+    width: 18px;
+}
+
+/* DATE AND TIME */
+
+p.datetime {
+    line-height: 20px;
+    margin: 0;
+    padding: 0;
+    color: var(--body-quiet-color);
+    font-weight: bold;
+}
+
+.datetime span {
+    white-space: nowrap;
+    font-weight: normal;
+    font-size: 0.6875rem;
+    color: var(--body-quiet-color);
+}
+
+.datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
+    margin-left: 5px;
+    margin-bottom: 4px;
+}
+
+table p.datetime {
+    font-size: 0.6875rem;
+    margin-left: 0;
+    padding-left: 0;
+}
+
+.datetimeshortcuts .clock-icon, .datetimeshortcuts .date-icon {
+    position: relative;
+    display: inline-block;
+    vertical-align: middle;
+    height: 16px;
+    width: 16px;
+    overflow: hidden;
+}
+
+.datetimeshortcuts .clock-icon {
+    background: url(../img/icon-clock.svg) 0 0 no-repeat;
+}
+
+.datetimeshortcuts a:focus .clock-icon,
+.datetimeshortcuts a:hover .clock-icon {
+    background-position: 0 -16px;
+}
+
+.datetimeshortcuts .date-icon {
+    background: url(../img/icon-calendar.svg) 0 0 no-repeat;
+    top: -1px;
+}
+
+.datetimeshortcuts a:focus .date-icon,
+.datetimeshortcuts a:hover .date-icon {
+    background-position: 0 -16px;
+}
+
+.timezonewarning {
+    font-size: 0.6875rem;
+    color: var(--body-quiet-color);
+}
+
+/* URL */
+
+p.url {
+    line-height: 20px;
+    margin: 0;
+    padding: 0;
+    color: var(--body-quiet-color);
+    font-size: 0.6875rem;
+    font-weight: bold;
+}
+
+.url a {
+    font-weight: normal;
+}
+
+/* FILE UPLOADS */
+
+p.file-upload {
+    line-height: 20px;
+    margin: 0;
+    padding: 0;
+    color: var(--body-quiet-color);
+    font-size: 0.6875rem;
+    font-weight: bold;
+}
+
+.aligned p.file-upload {
+    margin-left: 170px;
+}
+
+.file-upload a {
+    font-weight: normal;
+}
+
+.file-upload .deletelink {
+    margin-left: 5px;
+}
+
+span.clearable-file-input label {
+    color: var(--body-fg);
+    font-size: 0.6875rem;
+    display: inline;
+    float: none;
+}
+
+/* CALENDARS & CLOCKS */
+
+.calendarbox, .clockbox {
+    margin: 5px auto;
+    font-size: 0.75rem;
+    width: 19em;
+    text-align: center;
+    background: var(--body-bg);
+    color: var(--body-fg);
+    border: 1px solid var(--hairline-color);
+    border-radius: 4px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
+    overflow: hidden;
+    position: relative;
+}
+
+.clockbox {
+    width: auto;
+}
+
+.calendar {
+    margin: 0;
+    padding: 0;
+}
+
+.calendar table {
+    margin: 0;
+    padding: 0;
+    border-collapse: collapse;
+    background: white;
+    width: 100%;
+}
+
+.calendar caption, .calendarbox h2 {
+    margin: 0;
+    text-align: center;
+    border-top: none;
+    font-weight: 700;
+    font-size: 0.75rem;
+    color: #333;
+    background: var(--accent);
+}
+
+.calendar th {
+    padding: 8px 5px;
+    background: var(--darkened-bg);
+    border-bottom: 1px solid var(--border-color);
+    font-weight: 400;
+    font-size: 0.75rem;
+    text-align: center;
+    color: var(--body-quiet-color);
+}
+
+.calendar td {
+    font-weight: 400;
+    font-size: 0.75rem;
+    text-align: center;
+    padding: 0;
+    border-top: 1px solid var(--hairline-color);
+    border-bottom: none;
+}
+
+.calendar td.selected a {
+    background: var(--primary);
+    color: var(--button-fg);
+}
+
+.calendar td.nonday {
+    background: var(--darkened-bg);
+}
+
+.calendar td.today a {
+    font-weight: 700;
+}
+
+.calendar td a, .timelist a {
+    display: block;
+    font-weight: 400;
+    padding: 6px;
+    text-decoration: none;
+    color: var(--body-quiet-color);
+}
+
+.calendar td a:focus, .timelist a:focus,
+.calendar td a:hover, .timelist a:hover {
+    background: var(--primary);
+    color: white;
+}
+
+.calendar td a:active, .timelist a:active {
+    background: var(--header-bg);
+    color: white;
+}
+
+.calendarnav {
+    font-size: 0.625rem;
+    text-align: center;
+    color: #ccc;
+    margin: 0;
+    padding: 1px 3px;
+}
+
+.calendarnav a:link, #calendarnav a:visited,
+#calendarnav a:focus, #calendarnav a:hover {
+    color: var(--body-quiet-color);
+}
+
+.calendar-shortcuts {
+    background: var(--body-bg);
+    color: var(--body-quiet-color);
+    font-size: 0.6875rem;
+    line-height: 11px;
+    border-top: 1px solid var(--hairline-color);
+    padding: 8px 0;
+}
+
+.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
+    display: block;
+    position: absolute;
+    top: 8px;
+    width: 15px;
+    height: 15px;
+    text-indent: -9999px;
+    padding: 0;
+}
+
+.calendarnav-previous {
+    left: 10px;
+    background: url(../img/calendar-icons.svg) 0 0 no-repeat;
+}
+
+.calendarbox .calendarnav-previous:focus,
+.calendarbox .calendarnav-previous:hover {
+    background-position: 0 -15px;
+}
+
+.calendarnav-next {
+    right: 10px;
+    background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
+}
+
+.calendarbox .calendarnav-next:focus,
+.calendarbox .calendarnav-next:hover {
+    background-position: 0 -45px;
+}
+
+.calendar-cancel {
+    margin: 0;
+    padding: 4px 0;
+    font-size: 0.75rem;
+    background: #eee;
+    border-top: 1px solid var(--border-color);
+    color: var(--body-fg);
+}
+
+.calendar-cancel:focus, .calendar-cancel:hover {
+    background: #ddd;
+}
+
+.calendar-cancel a {
+    color: black;
+    display: block;
+}
+
+ul.timelist, .timelist li {
+    list-style-type: none;
+    margin: 0;
+    padding: 0;
+}
+
+.timelist a {
+    padding: 2px;
+}
+
+/* EDIT INLINE */
+
+.inline-deletelink {
+    float: right;
+    text-indent: -9999px;
+    background: url(../img/inline-delete.svg) 0 0 no-repeat;
+    width: 16px;
+    height: 16px;
+    border: 0px none;
+}
+
+.inline-deletelink:focus, .inline-deletelink:hover {
+    cursor: pointer;
+}
+
+/* RELATED WIDGET WRAPPER */
+.related-widget-wrapper {
+    float: left;       /* display properly in form rows with multiple fields */
+    overflow: hidden;  /* clear floated contents */
+}
+
+.related-widget-wrapper-link {
+    opacity: 0.3;
+}
+
+.related-widget-wrapper-link:link {
+    opacity: .8;
+}
+
+.related-widget-wrapper-link:link:focus,
+.related-widget-wrapper-link:link:hover {
+    opacity: 1;
+}
+
+select + .related-widget-wrapper-link,
+.related-widget-wrapper-link + .related-widget-wrapper-link {
+    margin-left: 7px;
+}

+ 202 - 0
static_new/admin/fonts/LICENSE.txt

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 0 - 0
static_new/admin/fonts/README.txt


Some files were not shown because too many files changed in this diff