ss="language-html">@@ -0,0 +1,22 @@ 1
+{% extends "front/base.html" %}
2
+{% load mptt_tags %}
3
+{% load static %}
4
+{% load crispy_forms_tags %}
5
+
6
+{% block content %}
7
+<h1>My Store</h1>
8
+{{ pos }}
9
+
10
+{% for p in primaryMenu %}
11
+    {{ p.name }}
12
+{% endfor %}
13
+
14
+{% include "front/_menu_ui.html" with menu=primaryMenu %}
15
+{{ storeInfo | safe }}
16
+
17
+<form  method="post">
18
+    {% csrf_token %}
19
+    {{ form  | crispy }}
20
+    <input type="submit" class='btn btn-primary' value="Save">
21
+</form>
22
+{% endblock %}

+ 30 - 0
app/front/templates/front/search.html

@@ -0,0 +1,30 @@
1
+{% extends "front/storeFront.html" %}
2
+{% load mptt_tags %}
3
+{% load static %}
4
+{% load crispy_forms_tags %}
5
+{% load pos_filters %}
6
+
7
+{% block content %}
8
+{% if results %}
9
+<h1 class='text-center mt-4 mb-4'>Search Results</h1>
10
+<div class='card-columns'>
11
+{% for r in results %}
12
+<div class="card">
13
+    {% if  r|className == "Post" %}
14
+    <img src="{{ r.featureImage.url }}" class="card-img-top" alt="...">
15
+    <div class="card-body">
16
+        <h5 class="card-title">
17
+            <a href="{% url "front:viewPost"   pid=r.id  %}?cstr={{ r.store.id }}">{{ r.title }}</a>
18
+        </h5>
19
+        <p class="card-text">
20
+        {{ r.description }}</p>
21
+        <p class="card-text"><small class="text-muted">{{ r.created_at }}</small></p>
22
+    </div>
23
+    {% endif %}
24
+</div>
25
+{% endfor %}
26
+</div>
27
+{% else %}
28
+<h1 class='text-center mt-4 mb-4'>Your Search not Matched any Records</h1>
29
+{% endif %}
30
+{% endblock %}

+ 77 - 0
app/front/templates/front/storeContact.html

@@ -0,0 +1,77 @@
1
+{% extends "front/storeFront.html" %}
2
+{% load mptt_tags %}
3
+{% load static %}
4
+{% load crispy_forms_tags %}
5
+
6
+{% block content %}
7
+<h1>{{ storeFront.fullName }}</h1>
8
+<div class='row'>
9
+    <div class='col-md-9'>
10
+        <p>
11
+        {{ storeFront.description }}
12
+        </p>
13
+        <div id='map'>
14
+        </div>
15
+    </div>
16
+    <div class='col-md-3'>
17
+        <dl>
18
+            <dt>Address</dt>
19
+            <dd>
20
+        {{ storeFront.addressText }}
21
+            </dd>
22
+            <dt>Tel</dt>
23
+            <dd>
24
+                <a href="callto:{{ storeFront.tel_number }}">
25
+                    {{ storeFront.tel_number}}
26
+                </a>
27
+            </dd>
28
+            <dt>Fax</dt>
29
+            <dd>
30
+                <a href="callto:{{ storeFront.fax_number }}">
31
+                    {{ storeFront.fax_number }}</a>
32
+            </dd>
33
+            <dt>LineID</dt>
34
+            <dd>
35
+                <a href="https://line.me/R/{{ storeFront.lineId }}">
36
+                    {{ storeFront.lineId }}</a>
37
+            </dd>
38
+            <dt>Email</dt>
39
+            <dd>
40
+                <a href="mailto:{{ storeFront.email }}">
41
+                    {{ storeFront.email }}</a>
42
+            </dd>
43
+        </dl>
44
+    </div>
45
+</div>
46
+{% endblock %}
47
+
48
+{% block header_script %}
49
+<style type='text/css'>
50
+#map {
51
+  height: 600px;
52
+}
53
+
54
+</style>
55
+{% endblock %}
56
+
57
+{% block foot_script %}
58
+    <script type='text/javascript'>
59
+        let map;
60
+
61
+        function initMap() {
62
+                let latLng = {lat: {{ geo.lat }}, lng: {{ geo.lon }} }; 
63
+                map = new google.maps.Map(document.getElementById("map"), {
64
+                    center: latLng,
65
+                    zoom: 8,
66
+                });
67
+                new google.maps.Marker({
68
+                        position: latLng,
69
+                        map,
70
+                        title: "{{ storeFront.fullName }}",
71
+                });
72
+        }
73
+                //initMap();
74
+        window.initMap = initMap;
75
+
76
+    </script>
77
+{% endblock %}

+ 34 - 0
app/front/templates/front/storeFront.html

@@ -0,0 +1,34 @@
1
+{% extends "front/base.html" %}
2
+{% load mptt_tags %}
3
+{% load static %}
4
+{% load crispy_forms_tags %}
5
+
6
+{% block content %}
7
+{% include "front/_localSearch.html" %}
8
+{% if storeFront %}
9
+
10
+<div class="card mb-3">
11
+    <div class="row no-gutters">
12
+        <div class="col-md-4">
13
+            {% if storeFront.logo %}
14
+            <img src="{{ storeFront.logo }}" class="rounded-circle" alt="Cinque Terre">
15
+            {% endif %}
16
+        </div>
17
+        <div class="col-md-8">
18
+            <div class="card-body">
19
+                <h5 class="card-title">{{ storeFront.fullName }}</h5>
20
+                <p class="card-text">
21
+                    {% firstof storeFront.description " " %}
22
+                </p>
23
+                <a class='btn btn-primary btn-sm' href="{% url  "front:viewStoreMap" %}?cstr={{ storeFront.storeId }}" target="_blank">View Map</a>
24
+                <a class='btn btn-danger btn-sm' href="">Follow</a>
25
+                <a class='btn btn-secondary btn-sm' href="">Share</a>
26
+            </div>
27
+        </div>
28
+    </div>
29
+</div>
30
+{% endif %}
31
+{% include "front/_promotions.html" with objs=promotions %}
32
+{% include "front/_recs.html" with objs=recs %}
33
+{% include "front/_reviews.html" with objs=reviews %}
34
+{% endblock %}

+ 95 - 0
app/front/templates/front/storeMap.html

@@ -0,0 +1,95 @@
1
+{% extends "front/storeFront.html" %}
2
+{% load mptt_tags %}
3
+{% load static %}
4
+{% load crispy_forms_tags %}
5
+
6
+{% block content %}
7
+<h1>{{ storeFront.fullName }}</h1>
8
+<div class='row'>
9
+    <div class='col-md-9'>
10
+        <p>
11
+        {{ storeFront.description }}
12
+        </p>
13
+        <form method='post' enctype="multipart/form-data" >
14
+            {% csrf_token %}
15
+            <div class="card w-100">
16
+                 <div class="card-header">Contact Us</div>
17
+                <div class="card-body">
18
+            <div class="row">
19
+            <div class="col-md-6">{{ form.subject | as_crispy_field }}</div>
20
+            <div class="col-md-6">{{ form.email | as_crispy_field }}</div>
21
+            <div class="col-md-6">{{ form.tel | as_crispy_field }}</div>
22
+            <div class="col-md-6">{{ form.lineId | as_crispy_field }}</div>
23
+            <div class="col-md-12">{{ form.body | as_crispy_field }}</div>
24
+            <div class="col-md-12">{{ form.messageFile | as_crispy_field }}</div>
25
+            </div>
26
+            <input type='submit' value='Save' class='btn btn-primary btn-block'>
27
+                </div>
28
+            </div>
29
+        </form>
30
+        <hr>
31
+        <div id='map'>
32
+        </div>
33
+    </div>
34
+    <div class='col-md-3'>
35
+        <dl>
36
+            <dt>Address</dt>
37
+            <dd>
38
+        {{ storeFront.addressText }}
39
+            </dd>
40
+            <dt>Tel</dt>
41
+            <dd>
42
+                <a href="callto:{{ storeFront.tel_number }}">
43
+                    {{ storeFront.tel_number}}
44
+                </a>
45
+            </dd>
46
+            <dt>Fax</dt>
47
+            <dd>
48
+                <a href="callto:{{ storeFront.fax_number }}">
49
+                    {{ storeFront.fax_number }}</a>
50
+            </dd>
51
+            <dt>LineID</dt>
52
+            <dd>
53
+                <a href="https://line.me/R/{{ storeFront.lineId }}">
54
+                    {{ storeFront.lineId }}</a>
55
+            </dd>
56
+            <dt>Email</dt>
57
+            <dd>
58
+                <a href="mailto:{{ storeFront.email }}">
59
+                    {{ storeFront.email }}</a>
60
+            </dd>
61
+        </dl>
62
+    </div>
63
+</div>
64
+{% endblock %}
65
+
66
+{% block header_script %}
67
+<style type='text/css'>
68
+#map {
69
+  height: 600px;
70
+}
71
+
72
+</style>
73
+{% endblock %}
74
+
75
+{% block foot_script %}
76
+    <script type='text/javascript'>
77
+        let map;
78
+
79
+        function initMap() {
80
+                let latLng = {lat: {{ geo.lat }}, lng: {{ geo.lon }} }; 
81
+                map = new google.maps.Map(document.getElementById("map"), {
82
+                    center: latLng,
83
+                    zoom: 8,
84
+                });
85
+                new google.maps.Marker({
86
+                        position: latLng,
87
+                        map,
88
+                        title: "{{ storeFront.fullName }}",
89
+                });
90
+        }
91
+                //initMap();
92
+        window.initMap = initMap;
93
+
94
+    </script>
95
+{% endblock %}

+ 18 - 0
app/front/templates/front/viewPost.html

@@ -0,0 +1,18 @@
1
+{% extends "front/storeFront.html" %}
2
+{% load mptt_tags %}
3
+{% load static %}
4
+{% load crispy_forms_tags %}
5
+
6
+{% block content %}
7
+<h1>{{ post.title }}</h1>
8
+<div class='row'>
9
+    <div class='col-md-9'>
10
+        <div class='content-wrap'>
11
+            {{ post.content | safe  }}
12
+        </div>
13
+    {% include "front/_relate_menus.html" with objs=post.relateMenus.all %}
14
+    </div>
15
+    <div class='col-md-3'>
16
+    </div>
17
+</div>
18
+{% endblock %}

+ 3 - 0
app/front/tests.py

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

+ 16 - 0
app/front/urls.py

@@ -0,0 +1,16 @@
1
+from django.urls import path
2
+
3
+from . import views
4
+
5
+urlpatterns = [
6
+    path('', views.index, name='index'),
7
+    path('search', views.search, name='search'),
8
+    path('myStore', views.myStore, name='myStore'),
9
+    path('store', views.storeFrontView, name='storeFront'),
10
+    path('store/<storeId>/menus', views.storeMenus, name='viewStoreMenus'),
11
+    path('menus/<menuId>', views.viewMenu, name='viewMenu'),
12
+    path('store/posts/<pid>', views.viewPost, name='viewPost'),
13
+    path('store/map', views.viewStoreMap, name='viewStoreMap'),
14
+    path('store/contact', views.storeContact, name='storeContact'),
15
+]
16
+app_name = 'front'

+ 87 - 0
app/front/views.py

@@ -0,0 +1,87 @@
1
+from django.shortcuts import render, redirect, reverse
2
+from django.contrib.auth.decorators import login_required
3
+from pos.pos import PosSys
4
+from pos.storefront import StoreFront
5
+from django.http import JsonResponse
6
+from django.contrib import messages
7
+# Create your views here.
8
+def index(request):
9
+    return render(request,'front/index.html')
10
+
11
+@login_required
12
+def myStore(request):
13
+    pos = PosSys.restoreStore(request.user)
14
+    storeInfo = pos.uiSys.renderStoreInfo()
15
+    primaryMenu = pos.uiSys.menu('primary')
16
+    formInstance = pos.uiSys.form('tableForm')
17
+
18
+    print("Instance")
19
+    print(formInstance)
20
+
21
+    if request.method == "POST":
22
+        formInstance = pos.uiSys.form('tableForm', request=request)
23
+        if formInstance.is_valid():
24
+            formInstance.save()
25
+            print("valid")
26
+        else:
27
+            print(formInstance.errors)
28
+            print("invalide")
29
+        return redirect('front:myStore')
30
+
31
+    return render(request, 'front/mystore.html', {'pos': pos, 'storeInfo': storeInfo, 'primaryMenu': primaryMenu, 'form': formInstance})
32
+
33
+def storeFrontView(request):
34
+    if request.storeFront:
35
+        menus = request.storeFront.getMenus()[:10]
36
+        promotions = request.storeFront.getPosts(cat='Promotions')
37
+        recs = request.storeFront.getPosts(cat="Recommendations")
38
+        reviews = request.storeFront.getPosts(cat="Reviews")
39
+        return render(request, 'front/storeFront.html', {'promotions': promotions, 'recs': recs, 'reviews': reviews, 'menus': menus})
40
+    return JsonResponse({'msg': "Please provide store id in querystring"})
41
+
42
+
43
+def storeMenus(request, storeId):
44
+    return render(request, 'front/storeMenus.html')
45
+
46
+def viewMenu(request, menuId):
47
+    return render(request, 'front/viewMenu.html')
48
+
49
+def viewPost(request, pid):
50
+    post = request.storeFront.getPostById(pid)
51
+    return render(request, 'front/viewPost.html', {'post': post})
52
+
53
+def viewStoreMap(request):
54
+    location = request.storeFront.location
55
+    form = request.storeFront.messageForm()
56
+    if request.method == 'POST':
57
+        form = request.storeFront.messageForm(request=request)
58
+        if form.is_valid():
59
+            o = form.save(commit=False)
60
+            o.store = request.storeFront.store
61
+            o.save()
62
+            messages.success(request, 'Thank you for your contact')
63
+            return redirect(reverse('front:viewStoreMap')+f"?cstr={request.storeFront.storeId}")
64
+    return render(request, 'front/storeMap.html', {'geo': location[1], 'address': location[0], 'form': form})
65
+
66
+
67
+def storeContact(request):
68
+    form = request.storeFront.messageForm()
69
+    if request.method == 'POST':
70
+        form = request.storeFront.messageForm(request=request)
71
+        if form.is_valid():
72
+            o = form.save()
73
+            messages.success(request, 'Thank you for your contact')
74
+            return redirect(reverse('front:storeContact')+"?cstr="+request.storeFront.storeId)
75
+    return render(request, 'front/storeContact.html', {'form': form})
76
+
77
+def search(request):
78
+    results  = None
79
+    q = request.GET.get('q', '')
80
+    print(q)
81
+    if request.storeFront:
82
+        results = request.storeFront.search(q=q)
83
+    else:
84
+        results = StoreFront.globalSearch(q)
85
+        print(results)
86
+    return render(request, 'front/search.html', {'results': results})
87
+

+ 28 - 0
app/key.pem

@@ -0,0 +1,28 @@
1
+-----BEGIN PRIVATE KEY-----
2
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDM7eQObxKic7Y/
3
++lI5CYU9iiPb5/PycR3myz68v95FzkFomyrXrnWCUh07JRfXcHgTS+JMXRprpI/2
4
+NktTqU+W4nBblRgK5VJbrvAvQS8LzZMGkHPaEwa4LoNykbScmDNjTiS7PSYlqpa1
5
+YPgAyrcE7CAE3LY+sHjlxQYEF7du+wKKn3KRVby8cRYqAel2dm9Ridttt5r2MNH+
6
+cHRwN4p3krjlX8u/YQIEAZrbvYL1r26pe7MhxUWx05v2yzpg3KJKzUAl5dNXXozm
7
+7pmVU33khT0AepbF5MJVjIZlZm2DVcPswH8zbb+YNSNmDV90re/af5EXHKj8Uag0
8
+TrOIALPrAgMBAAECggEBALGtmMwC9c8wMFYsPVoCrSl8OjcSV2pfNSPEGLMiUB+K
9
+AyAlWPID6xKBC6MaOB+s/g8M/jpjhuLJnaBF1u3EoKMb1XsyO9RGnC+t78Wo6Jd9
10
+N/q7CBeN44eRnJqbRlN3iyaQvDwzen2x+FVuq9hT6nc0G1bb3o9gBpKBTwQBZCOt
11
+o4djb1dkSx3qB917VC4+0yK1pknliANxxdVcq8ZHfcY3G7xkgV8crn5UB95HmFAp
12
+9sJ1XxpXHqoD+lgLakQKhgHOTqQdb0mbMOPkHA5Zw2YRV89FaYc2m+if3rWyaK4z
13
+yk9hSd8/LK71rmYO0YlVDGwWkhUkDAD3q/T20kpeFxECgYEA/Uf5PqKc3Z9k6bTp
14
+3JnfMNrXY4Nk1pE8apZF1Saho8mKsCX0Vm3x4DC8xZgwE5gvMusopHWp/EoCvMet
15
+5/EJpNXzjLZB35IdAiGCYQPLEQTQjmCtsQIRoknhZc9nKPw/iWlev68Yrrf16jkj
16
+9FrvyyFEWQ4wUgQftPC/7u5Ad58CgYEAzyELXIxtXpXdjch0X9L7IRULuQ4Rs7q3
17
+7+TvFYd5P0X2zBBJ7AHjamaru7C+ytnT7bpSudRv36k9KySsHtqGKl9uSJrGGJeW
18
+kqQ9wOorwu9PDVvdL2DxG31bK5b+BcQhqSPYJEVe3FpETJgtbFNIIf9LEVg73v91
19
+iNiNqbwSEDUCgYEAq7poCQrSVwWisz7BrZv6kzJeBY/qB/1TPGWFFZ9qyxV0Xjht
20
+sUg8TihdZY/pUO/HWLvOw6svxOodbwfoJrHsOwIBbu+IPGDiIDa+Iq8iuPhNu6tb
21
+OP/RGvsCwzfblxNotO9nmYnLr3L1Xoi9kwkxOsXkhIk1Q/ad1N3DFOofdbsCgYAq
22
+X2kymqu5INF9MtfTzpZ/Uw3d4qnuabE9S0k5z0gXkJmHb4Gf3VcHqk9RizvMxbkc
23
+NfS8fWARkk6oJ81qVmwB+RnXkooZ99De2OilMYKYU1qJshRSn/NTG1buWOpIhbIZ
24
+JvMNoH9idrjoLm2EbpkgE1jpCHLfEMWbpCl+4rGTTQKBgQCvOqqWpwS0fJFwbx1h
25
+F+jcLf9eQHD5B/kh91f4dvB3Uywi2vP5pkAPkRS2dd6z5ZKNuHCCUYizKDR6d20g
26
+EnGUuN96uyby4vT5Xar1/6IWcJl2VpjHAUjGAKw2PHRA4ti+RO7oo4TP9J/tF/Ec
27
+xoO0p1jYKttMAiKBlw10sNBZmg==
28
+-----END PRIVATE KEY-----

+ 22 - 0
app/manage.py

@@ -0,0 +1,22 @@
1
+#!/usr/bin/env python
2
+"""Django's command-line utility for administrative tasks."""
3
+import os
4
+import sys
5
+
6
+
7
+def main():
8
+    """Run administrative tasks."""
9
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'simpliPos.settings')
10
+    try:
11
+        from django.core.management import execute_from_command_line
12
+    except ImportError as exc:
13
+        raise ImportError(
14
+            "Couldn't import Django. Are you sure it's installed and "
15
+            "available on your PYTHONPATH environment variable? Did you "
16
+            "forget to activate a virtual environment?"
17
+        ) from exc
18
+    execute_from_command_line(sys.argv)
19
+
20
+
21
+if __name__ == '__main__':
22
+    main()

BIN
app/mycert.cer


+ 0 - 0
app/pos/__init__.py


+ 70 - 0
app/pos/admin.py

@@ -0,0 +1,70 @@
1
+from django.contrib import admin
2
+from .models import Store, MenuItem, Setting, StoreFile, Table, BookingTable, Order,  MenuFile, OrderItem, UIMenu, PostCategory, Post
3
+from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
4
+from mptt.admin import MPTTModelAdmin
5
+from django_admin_hstore_widget.forms import HStoreFormField
6
+
7
+from django.contrib.postgres.fields import HStoreField, ArrayField
8
+
9
+from django import forms
10
+
11
+
12
+
13
+
14
+# Register your models here.
15
+
16
+class StoreFileAdmin(admin.TabularInline):
17
+    model = StoreFile
18
+
19
+class MenuItemAdmin(admin.StackedInline):
20
+    model = MenuItem
21
+
22
+class OrderItemAdmin(admin.TabularInline):
23
+    model = OrderItem
24
+
25
+class StoreAdmin(admin.ModelAdmin):
26
+   inlines = [StoreFileAdmin, MenuItemAdmin]
27
+   formfield_overrides = {
28
+        HStoreField: {'widget': HStoreFormField},
29
+    }
30
+
31
+class SettingAdmin(admin.ModelAdmin):
32
+    pass
33
+
34
+class OrderAdmin(admin.ModelAdmin):
35
+   inlines = [OrderItemAdmin,]
36
+
37
+
38
+class SettingAdminForm(forms.ModelForm):
39
+    data = HStoreFormField()
40
+
41
+    class Meta:
42
+       model = Setting
43
+       exclude = ()
44
+
45
+@admin.register(Setting)
46
+class SettingAdmin(admin.ModelAdmin):
47
+    form = SettingAdminForm
48
+
49
+@admin.register(Post)
50
+class PostAdmin(admin.ModelAdmin):
51
+    list_display = ('title', 'tag_list', 'cats', 'created_at', 'store', 'featureImage')
52
+    save_as = True
53
+
54
+    def tag_list(self, obj):
55
+        return u", ".join(o.name for o in obj.tags.all())
56
+
57
+
58
+admin.site.register(Store, StoreAdmin)
59
+admin.site.register(MenuItem)
60
+#admin.site.register(Setting, SettingAdmin)
61
+admin.site.register(StoreFile)
62
+admin.site.register(Table)
63
+admin.site.register(BookingTable)
64
+admin.site.register(Order, OrderAdmin)
65
+admin.site.register(MenuFile)
66
+admin.site.register(UIMenu, MPTTModelAdmin)
67
+#admin.site.register(Post)
68
+admin.site.register(PostCategory, MPTTModelAdmin)
69
+
70
+

+ 6 - 0
app/pos/apps.py

@@ -0,0 +1,6 @@
1
+from django.apps import AppConfig
2
+
3
+
4
+class PosConfig(AppConfig):
5
+    default_auto_field = 'django.db.models.BigAutoField'
6
+    name = 'pos'

+ 53 - 0
app/pos/context_processors.py

@@ -0,0 +1,53 @@
1
+from django.conf import settings
2
+from .pos import PosSys
3
+from .storefront import StoreFront
4
+import json
5
+from .models import Store
6
+
7
+def pos(request):
8
+    store = Store.objects.get(pk=settings.STORE_ID)
9
+    cstr = request.GET.get('cstr', None)
10
+    if cstr:
11
+        storeFront = StoreFront(int(cstr))
12
+    else:
13
+        storeFront = None
14
+
15
+    if hasattr(request, 'user') and request.user.is_authenticated:
16
+        user = request.user
17
+        pos = PosSys.restoreStore(user)
18
+        storeInfo = pos.uiSys.renderStoreInfo()
19
+
20
+        #ms = jso.loads(pos.settingSys.get('menus'))
21
+        ms = []
22
+        try:
23
+            ms = json.loads(pos.settingSys.get('menus'))
24
+        except TypeError:
25
+            ms = []
26
+
27
+        print(ms)
28
+        print(type(ms))
29
+        temp = []
30
+        for i in ms:
31
+            temp.append({'title': i['title'], 'menu': pos.uiSys.menu(i['name'])})
32
+            #ms.append(pos.uiSys.menu(m))
33
+
34
+        ctx  = {
35
+            'pos': pos,
36
+            'info': storeInfo,
37
+            'menus': temp,
38
+            'primaryMenu': pos.uiSys.menu('primary'),
39
+            'store': store,
40
+            'storeFront': storeFront,
41
+        }
42
+        return ctx
43
+    else:
44
+        ctx = {
45
+            'pos': None,
46
+            'info': None,
47
+            'menus': [],
48
+            'store': store,
49
+            'storeFront': storeFront,
50
+        }
51
+        return ctx
52
+
53
+

+ 159 - 0
app/pos/forms.py

@@ -0,0 +1,159 @@
1
+from pos.models import MenuItem, Store, Setting, Table, BookingTable, Order, OrderItem, UIMenu, Post, PostCategory, Customer, Code, Profile, Message
2
+
3
+from django.forms import ModelForm
4
+import django_filters
5
+
6
+from django.forms import BaseInlineFormSet, DateField, TimeField, TextInput, ModelMultipleChoiceField, CheckboxSelectMultiple
7
+
8
+from mptt.forms import TreeNodeMultipleChoiceField
9
+from django_editorjs_fields import EditorJsWidget
10
+from django.contrib.auth.models import User
11
+from django_google_maps.widgets import GoogleMapsAddressWidget
12
+
13
+class TableForm(ModelForm):
14
+    class Meta:
15
+        model = Table
16
+        fields = ('code', 'nSeats', 'occupy',)
17
+
18
+class MessageForm(ModelForm):
19
+    class Meta:
20
+        model = Message
21
+        fields = ('subject', 'email', 'tel', 'body', 'messageFile', 'lineId', 'reply', 'parent', )
22
+
23
+class UIMenuForm(ModelForm):
24
+    class Meta:
25
+        model = UIMenu
26
+        fields = ('name', 'parent', 'group', 'url', 'icon', 'nOrder', )
27
+
28
+class OrderForm(ModelForm):
29
+    class Meta:
30
+        model = Order
31
+        fields = '__all__'
32
+
33
+class PostForm(ModelForm):
34
+    #category = TreeNodeMultipleChoiceField(queryset=PostCategory.objects.all())
35
+    '''
36
+    relateMenus = ModelMultipleChoiceField(
37
+        queryset=MenuItem.objects.all(),
38
+        widget=CheckboxSelectMultiple,
39
+        required=False)
40
+    '''
41
+
42
+    class Meta:
43
+        model = Post
44
+        fields = ('featureImage', 'title', 'content', 'description', 'category', 'tags', 'relateMenus' )
45
+
46
+class CustomerForm(ModelForm):
47
+    #category = TreeNodeMultipleChoiceField(queryset=PostCategory.objects.all())
48
+
49
+    class Meta:
50
+        model = Customer
51
+        fields = ('fullName', 'email', 'lineId', 'gender', )
52
+
53
+class BookingForm(ModelForm):
54
+    onDate = DateField(
55
+        widget=TextInput(
56
+            attrs={'type': 'date'}
57
+        )
58
+    )
59
+    onTime = TimeField(
60
+        widget=TextInput(
61
+            attrs={'type': 'time'}
62
+        )
63
+    )
64
+    class Meta:
65
+        model = BookingTable
66
+        fields = ('table', 'onDate',  'onTime', 'note',)
67
+
68
+class MenuItemForm(ModelForm):
69
+    class Meta:
70
+        model = MenuItem
71
+        fields = ['featureImage', 'name', ]
72
+
73
+class StoreForm(ModelForm):
74
+    class Meta:
75
+        model = Store
76
+        fields = ['logo', 'name','description', 'addressText', 'province','address',  'phone_number', 'fax_number', 'lineId',  'email', 'geolocation',  'storeType']
77
+
78
+        widgets = {
79
+            "address": GoogleMapsAddressWidget,
80
+        }
81
+
82
+class OrderItemFormSet(BaseInlineFormSet):
83
+    def __init__(self, other_model_queryset, *args, **kwargs):
84
+        super(BaseInlineFormSet, self).__init__(*args)
85
+
86
+        for form in self.forms:
87
+            form.fields['item'].queryset = other_model_queryset
88
+
89
+
90
+formMap = {
91
+    'tableForm': TableForm
92
+}
93
+
94
+class OrderFilter(django_filters.FilterSet):
95
+    note = django_filters.CharFilter(lookup_expr='iexact')
96
+
97
+    class Meta:
98
+        model = Order
99
+        fields = ['table', 'note']
100
+
101
+class MenuItemFilter(django_filters.FilterSet):
102
+    class Meta:
103
+        model = MenuItem
104
+        fields = ['name', ]
105
+
106
+class StoreFilter(django_filters.FilterSet):
107
+    class Meta:
108
+        model = Store
109
+        fields = ('name', 'address', 'province','storeType')
110
+
111
+class BookingFilter(django_filters.FilterSet):
112
+    class Meta:
113
+        model = BookingTable
114
+        fields = ('table', 'onDate', 'note', )
115
+
116
+class CustomerFilter(django_filters.FilterSet):
117
+    class Meta:
118
+        model = Customer
119
+        fields = ('fullName', 'email', 'lineId', 'gender', 'created_at', )
120
+
121
+class TableFilter(django_filters.FilterSet):
122
+    class Meta:
123
+        model = Table
124
+        fields = ('code', 'nSeats', 'occupy', )
125
+
126
+class UIMenuFilter(django_filters.FilterSet):
127
+    class Meta:
128
+        model = UIMenu
129
+        fields = ('name', 'parent', 'group', 'url', 'icon','nOrder')
130
+
131
+class MessageFilter(django_filters.FilterSet):
132
+    class Meta:
133
+        model = Message
134
+        fields = ('subject', 'email', 'tel', 'body', 'reply', 'parent', 'created_at',  )
135
+
136
+class PostFilter(django_filters.FilterSet):
137
+    class Meta:
138
+        model = Post
139
+        fields = ('title', 'content', )
140
+
141
+class UserFilter(django_filters.FilterSet):
142
+    profile__role = django_filters.ChoiceFilter(label="Role", choices=Profile.ROLE_CHOICES)
143
+
144
+    class Meta:
145
+        model = User
146
+        fields = ('username', 'first_name', 'last_name', 'email', )
147
+
148
+
149
+
150
+
151
+class CodeForm(ModelForm):
152
+    class Meta:
153
+        model = Code
154
+        exclude = ['store', 'owner',]
155
+        widgets = {
156
+            'body_editorjs': EditorJsWidget(config={'minHeight': 100}),
157
+            'body_editorjs_text': EditorJsWidget(plugins=["@editorjs/image", "@editorjs/header"]),
158
+        }
159
+

+ 22 - 0
app/pos/middleware.py

@@ -0,0 +1,22 @@
1
+from django.utils.deprecation import MiddlewareMixin
2
+from .pos import PosSys
3
+from .storefront import StoreFront
4
+
5
+class PosMiddleware(MiddlewareMixin):
6
+    def process_request(self, request):
7
+        if not hasattr(request, "user"):
8
+            raise ImproperlyConfigured(
9
+                "The Django POS middleware requires auth middleware")
10
+        user = request.user
11
+        if user.is_authenticated:
12
+            if not hasattr(request, 'pos') or request.pos is None:
13
+                pos = PosSys.restoreStore(user)
14
+                request.pos = pos
15
+        else:
16
+            request.pos = None
17
+
18
+        cstr  = request.GET.get('cstr', None)
19
+        if cstr:
20
+            request.storeFront = StoreFront(int(cstr))
21
+        else:
22
+            request.storeFront = None

+ 22 - 0
app/pos/migrations/0001_initial.py

@@ -0,0 +1,22 @@
1
+# Generated by Django 3.2.13 on 2022-05-14 08:48
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    initial = True
9
+
10
+    dependencies = [
11
+    ]
12
+
13
+    operations = [
14
+        migrations.CreateModel(
15
+            name='MenuItem',
16
+            fields=[
17
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+                ('name', models.CharField(max_length=100)),
19
+                ('tagline', models.TextField()),
20
+            ],
21
+        ),
22
+    ]

+ 31 - 0
app/pos/migrations/0002_setting_store.py

@@ -0,0 +1,31 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 06:15
2
+
3
+import django.contrib.postgres.fields.hstore
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+
7
+from django.contrib.postgres.operations import HStoreExtension
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    dependencies = [
12
+        ('pos', '0001_initial'),
13
+    ]
14
+
15
+    operations = [
16
+        HStoreExtension(),
17
+        migrations.CreateModel(
18
+            name='Store',
19
+            fields=[
20
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
+                ('name', models.CharField(max_length=100)),
22
+            ],
23
+        ),
24
+        migrations.CreateModel(
25
+            name='Setting',
26
+            fields=[
27
+                ('store', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='pos.store')),
28
+                ('data', django.contrib.postgres.fields.hstore.HStoreField()),
29
+            ],
30
+        ),
31
+    ]

+ 19 - 0
app/pos/migrations/0003_alter_setting_data.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 06:55
2
+
3
+import django.contrib.postgres.fields.hstore
4
+from django.db import migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0002_setting_store'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AlterField(
15
+            model_name='setting',
16
+            name='data',
17
+            field=django.contrib.postgres.fields.hstore.HStoreField(blank=True),
18
+        ),
19
+    ]

+ 31 - 0
app/pos/migrations/0004_auto_20220517_1613.py

@@ -0,0 +1,31 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 09:13
2
+
3
+import django.contrib.postgres.fields
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('pos', '0003_alter_setting_data'),
12
+    ]
13
+
14
+    operations = [
15
+        migrations.AddField(
16
+            model_name='menuitem',
17
+            name='photos',
18
+            field=django.contrib.postgres.fields.ArrayField(base_field=models.FileField(upload_to='uploads/%Y/%m/%d/'), blank=True, null=True, size=8),
19
+        ),
20
+        migrations.AddField(
21
+            model_name='menuitem',
22
+            name='store',
23
+            field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='pos.store'),
24
+            preserve_default=False,
25
+        ),
26
+        migrations.AddField(
27
+            model_name='store',
28
+            name='photos',
29
+            field=django.contrib.postgres.fields.ArrayField(base_field=models.FileField(upload_to='uploads/%Y/%m/%d/'), blank=True, null=True, size=8),
30
+        ),
31
+    ]

+ 38 - 0
app/pos/migrations/0005_auto_20220517_2027.py

@@ -0,0 +1,38 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 13:27
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+import taggit.managers
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('taggit', '0005_auto_20220424_2025'),
12
+        ('pos', '0004_auto_20220517_1613'),
13
+    ]
14
+
15
+    operations = [
16
+        migrations.RemoveField(
17
+            model_name='menuitem',
18
+            name='photos',
19
+        ),
20
+        migrations.CreateModel(
21
+            name='StoreFile',
22
+            fields=[
23
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24
+                ('storeFile', models.FileField(upload_to='uploads/%Y/%m/%d/')),
25
+                ('store', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pos.store')),
26
+                ('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
27
+            ],
28
+        ),
29
+        migrations.CreateModel(
30
+            name='MenuFile',
31
+            fields=[
32
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
33
+                ('menuFile', models.FileField(upload_to='uploads/%Y/%m/%d/')),
34
+                ('store', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pos.store')),
35
+                ('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
36
+            ],
37
+        ),
38
+    ]

+ 17 - 0
app/pos/migrations/0006_remove_store_photos.py

@@ -0,0 +1,17 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 13:28
2
+
3
+from django.db import migrations
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0005_auto_20220517_2027'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.RemoveField(
14
+            model_name='store',
15
+            name='photos',
16
+        ),
17
+    ]

+ 25 - 0
app/pos/migrations/0007_auto_20220517_2038.py

@@ -0,0 +1,25 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 13:38
2
+
3
+from django.db import migrations, models
4
+import django.utils.timezone
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0006_remove_store_photos'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='menuitem',
16
+            name='created_at',
17
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
18
+            preserve_default=False,
19
+        ),
20
+        migrations.AddField(
21
+            model_name='menuitem',
22
+            name='updated_at',
23
+            field=models.DateTimeField(auto_now=True),
24
+        ),
25
+    ]

+ 58 - 0
app/pos/migrations/0008_auto_20220517_2040.py

@@ -0,0 +1,58 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 13:40
2
+
3
+from django.db import migrations, models
4
+import django.utils.timezone
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0007_auto_20220517_2038'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='menufile',
16
+            name='created_at',
17
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
18
+            preserve_default=False,
19
+        ),
20
+        migrations.AddField(
21
+            model_name='menufile',
22
+            name='updated_at',
23
+            field=models.DateTimeField(auto_now=True),
24
+        ),
25
+        migrations.AddField(
26
+            model_name='setting',
27
+            name='created_at',
28
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
29
+            preserve_default=False,
30
+        ),
31
+        migrations.AddField(
32
+            model_name='setting',
33
+            name='updated_at',
34
+            field=models.DateTimeField(auto_now=True),
35
+        ),
36
+        migrations.AddField(
37
+            model_name='store',
38
+            name='created_at',
39
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
40
+            preserve_default=False,
41
+        ),
42
+        migrations.AddField(
43
+            model_name='store',
44
+            name='updated_at',
45
+            field=models.DateTimeField(auto_now=True),
46
+        ),
47
+        migrations.AddField(
48
+            model_name='storefile',
49
+            name='created_at',
50
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
51
+            preserve_default=False,
52
+        ),
53
+        migrations.AddField(
54
+            model_name='storefile',
55
+            name='updated_at',
56
+            field=models.DateTimeField(auto_now=True),
57
+        ),
58
+    ]

+ 55 - 0
app/pos/migrations/0009_bookingtable_order_table.py

@@ -0,0 +1,55 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 15:57
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0008_auto_20220517_2040'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.CreateModel(
15
+            name='Table',
16
+            fields=[
17
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+                ('created_at', models.DateTimeField(auto_now_add=True)),
19
+                ('updated_at', models.DateTimeField(auto_now=True)),
20
+                ('nSeats', models.IntegerField(default=1)),
21
+                ('occupy', models.BooleanField(default=False)),
22
+                ('store', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pos.store')),
23
+            ],
24
+            options={
25
+                'abstract': False,
26
+            },
27
+        ),
28
+        migrations.CreateModel(
29
+            name='Order',
30
+            fields=[
31
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
32
+                ('created_at', models.DateTimeField(auto_now_add=True)),
33
+                ('updated_at', models.DateTimeField(auto_now=True)),
34
+                ('items', models.ManyToManyField(to='pos.MenuItem')),
35
+                ('table', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='pos.table')),
36
+            ],
37
+            options={
38
+                'abstract': False,
39
+            },
40
+        ),
41
+        migrations.CreateModel(
42
+            name='BookingTable',
43
+            fields=[
44
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
45
+                ('created_at', models.DateTimeField(auto_now_add=True)),
46
+                ('updated_at', models.DateTimeField(auto_now=True)),
47
+                ('onDate', models.DateTimeField()),
48
+                ('note', models.TextField(blank=True, null=True)),
49
+                ('table', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pos.table')),
50
+            ],
51
+            options={
52
+                'abstract': False,
53
+            },
54
+        ),
55
+    ]

+ 18 - 0
app/pos/migrations/0010_table_code.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 16:02
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0009_bookingtable_order_table'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='table',
15
+            name='code',
16
+            field=models.CharField(default='N\\A', max_length=100),
17
+        ),
18
+    ]

+ 32 - 0
app/pos/migrations/0011_auto_20220517_2307.py

@@ -0,0 +1,32 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 16:07
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0010_table_code'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.RemoveField(
15
+            model_name='order',
16
+            name='items',
17
+        ),
18
+        migrations.CreateModel(
19
+            name='OrderItem',
20
+            fields=[
21
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22
+                ('created_at', models.DateTimeField(auto_now_add=True)),
23
+                ('updated_at', models.DateTimeField(auto_now=True)),
24
+                ('qty', models.IntegerField(default=1)),
25
+                ('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pos.menuitem')),
26
+                ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pos.order')),
27
+            ],
28
+            options={
29
+                'abstract': False,
30
+            },
31
+        ),
32
+    ]

+ 18 - 0
app/pos/migrations/0012_alter_menuitem_tagline.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-05-17 16:12
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0011_auto_20220517_2307'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterField(
14
+            model_name='menuitem',
15
+            name='tagline',
16
+            field=models.TextField(blank=True, null=True),
17
+        ),
18
+    ]

+ 103 - 0
app/pos/migrations/0013_auto_20220518_1430.py

@@ -0,0 +1,103 @@
1
+# Generated by Django 3.2.13 on 2022-05-18 07:30
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0012_alter_menuitem_tagline'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='bookingtable',
15
+            name='inProcess',
16
+            field=models.CharField(blank=True, max_length=100, null=True),
17
+        ),
18
+        migrations.AddField(
19
+            model_name='bookingtable',
20
+            name='status',
21
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
22
+        ),
23
+        migrations.AddField(
24
+            model_name='menufile',
25
+            name='inProcess',
26
+            field=models.CharField(blank=True, max_length=100, null=True),
27
+        ),
28
+        migrations.AddField(
29
+            model_name='menufile',
30
+            name='status',
31
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
32
+        ),
33
+        migrations.AddField(
34
+            model_name='menuitem',
35
+            name='inProcess',
36
+            field=models.CharField(blank=True, max_length=100, null=True),
37
+        ),
38
+        migrations.AddField(
39
+            model_name='menuitem',
40
+            name='status',
41
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
42
+        ),
43
+        migrations.AddField(
44
+            model_name='order',
45
+            name='inProcess',
46
+            field=models.CharField(blank=True, max_length=100, null=True),
47
+        ),
48
+        migrations.AddField(
49
+            model_name='order',
50
+            name='status',
51
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
52
+        ),
53
+        migrations.AddField(
54
+            model_name='orderitem',
55
+            name='inProcess',
56
+            field=models.CharField(blank=True, max_length=100, null=True),
57
+        ),
58
+        migrations.AddField(
59
+            model_name='orderitem',
60
+            name='status',
61
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
62
+        ),
63
+        migrations.AddField(
64
+            model_name='setting',
65
+            name='inProcess',
66
+            field=models.CharField(blank=True, max_length=100, null=True),
67
+        ),
68
+        migrations.AddField(
69
+            model_name='setting',
70
+            name='status',
71
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
72
+        ),
73
+        migrations.AddField(
74
+            model_name='store',
75
+            name='inProcess',
76
+            field=models.CharField(blank=True, max_length=100, null=True),
77
+        ),
78
+        migrations.AddField(
79
+            model_name='store',
80
+            name='status',
81
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
82
+        ),
83
+        migrations.AddField(
84
+            model_name='storefile',
85
+            name='inProcess',
86
+            field=models.CharField(blank=True, max_length=100, null=True),
87
+        ),
88
+        migrations.AddField(
89
+            model_name='storefile',
90
+            name='status',
91
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
92
+        ),
93
+        migrations.AddField(
94
+            model_name='table',
95
+            name='inProcess',
96
+            field=models.CharField(blank=True, max_length=100, null=True),
97
+        ),
98
+        migrations.AddField(
99
+            model_name='table',
100
+            name='status',
101
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100),
102
+        ),
103
+    ]

+ 18 - 0
app/pos/migrations/0014_order_note.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-05-19 14:23
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0013_auto_20220518_1430'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='order',
15
+            name='note',
16
+            field=models.TextField(blank=True, null=True),
17
+        ),
18
+    ]

+ 32 - 0
app/pos/migrations/0015_ingredient.py

@@ -0,0 +1,32 @@
1
+# Generated by Django 3.2.13 on 2022-05-19 14:30
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0014_order_note'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.CreateModel(
15
+            name='Ingredient',
16
+            fields=[
17
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+                ('created_at', models.DateTimeField(auto_now_add=True)),
19
+                ('updated_at', models.DateTimeField(auto_now=True)),
20
+                ('inProcess', models.CharField(blank=True, max_length=100, null=True)),
21
+                ('status', models.CharField(choices=[('process', 'Process'), ('fn', 'Finished')], default='process', max_length=100)),
22
+                ('name', models.CharField(max_length=200)),
23
+                ('qty', models.DecimalField(decimal_places=2, max_digits=5)),
24
+                ('unit', models.CharField(max_length=10)),
25
+                ('comment', models.TextField(blank=True, null=True)),
26
+                ('menu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pos.menuitem')),
27
+            ],
28
+            options={
29
+                'abstract': False,
30
+            },
31
+        ),
32
+    ]

+ 26 - 0
app/pos/migrations/0016_profile.py

@@ -0,0 +1,26 @@
1
+# Generated by Django 3.2.13 on 2022-05-19 14:40
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+        ('pos', '0015_ingredient'),
13
+    ]
14
+
15
+    operations = [
16
+        migrations.CreateModel(
17
+            name='Profile',
18
+            fields=[
19
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
+                ('location', models.CharField(blank=True, max_length=30)),
21
+                ('birthdate', models.DateField(blank=True, null=True)),
22
+                ('role', models.PositiveSmallIntegerField(blank=True, choices=[(1, 'Store Owner'), (2, 'Chef'), (3, 'Employee')], null=True)),
23
+                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
24
+            ],
25
+        ),
26
+    ]

+ 66 - 0
app/pos/migrations/0017_auto_20220519_2146.py

@@ -0,0 +1,66 @@
1
+# Generated by Django 3.2.13 on 2022-05-19 14:46
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+        ('pos', '0016_profile'),
13
+    ]
14
+
15
+    operations = [
16
+        migrations.AddField(
17
+            model_name='bookingtable',
18
+            name='owner',
19
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
20
+        ),
21
+        migrations.AddField(
22
+            model_name='ingredient',
23
+            name='owner',
24
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
25
+        ),
26
+        migrations.AddField(
27
+            model_name='menufile',
28
+            name='owner',
29
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
30
+        ),
31
+        migrations.AddField(
32
+            model_name='menuitem',
33
+            name='owner',
34
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
35
+        ),
36
+        migrations.AddField(
37
+            model_name='order',
38
+            name='owner',
39
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
40
+        ),
41
+        migrations.AddField(
42
+            model_name='orderitem',
43
+            name='owner',
44
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
45
+        ),
46
+        migrations.AddField(
47
+            model_name='setting',
48
+            name='owner',
49
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
50
+        ),
51
+        migrations.AddField(
52
+            model_name='store',
53
+            name='owner',
54
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
55
+        ),
56
+        migrations.AddField(
57
+            model_name='storefile',
58
+            name='owner',
59
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
60
+        ),
61
+        migrations.AddField(
62
+            model_name='table',
63
+            name='owner',
64
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
65
+        ),
66
+    ]

+ 68 - 0
app/pos/migrations/0018_auto_20220520_2029.py

@@ -0,0 +1,68 @@
1
+# Generated by Django 3.2.13 on 2022-05-20 13:29
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0017_auto_20220519_2146'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='orderitem',
15
+            name='note',
16
+            field=models.TextField(blank=True, null=True),
17
+        ),
18
+        migrations.AlterField(
19
+            model_name='bookingtable',
20
+            name='status',
21
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
22
+        ),
23
+        migrations.AlterField(
24
+            model_name='ingredient',
25
+            name='status',
26
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
27
+        ),
28
+        migrations.AlterField(
29
+            model_name='menufile',
30
+            name='status',
31
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
32
+        ),
33
+        migrations.AlterField(
34
+            model_name='menuitem',
35
+            name='status',
36
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
37
+        ),
38
+        migrations.AlterField(
39
+            model_name='order',
40
+            name='status',
41
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
42
+        ),
43
+        migrations.AlterField(
44
+            model_name='orderitem',
45
+            name='status',
46
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
47
+        ),
48
+        migrations.AlterField(
49
+            model_name='setting',
50
+            name='status',
51
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
52
+        ),
53
+        migrations.AlterField(
54
+            model_name='store',
55
+            name='status',
56
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
57
+        ),
58
+        migrations.AlterField(
59
+            model_name='storefile',
60
+            name='status',
61
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
62
+        ),
63
+        migrations.AlterField(
64
+            model_name='table',
65
+            name='status',
66
+            field=models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100),
67
+        ),
68
+    ]

+ 39 - 0
app/pos/migrations/0019_uimenu.py

@@ -0,0 +1,39 @@
1
+# Generated by Django 3.2.13 on 2022-05-21 03:32
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+import mptt.fields
7
+
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    dependencies = [
12
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+        ('pos', '0018_auto_20220520_2029'),
14
+    ]
15
+
16
+    operations = [
17
+        migrations.CreateModel(
18
+            name='UIMenu',
19
+            fields=[
20
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
+                ('created_at', models.DateTimeField(auto_now_add=True)),
22
+                ('updated_at', models.DateTimeField(auto_now=True)),
23
+                ('inProcess', models.CharField(blank=True, max_length=100, null=True)),
24
+                ('status', models.CharField(choices=[('process', 'Process'), ('fn', 'Finished'), ('purchase', 'Purchase')], default='process', max_length=100)),
25
+                ('name', models.CharField(max_length=200)),
26
+                ('group', models.CharField(max_length=200)),
27
+                ('url', models.URLField()),
28
+                ('lft', models.PositiveIntegerField(editable=False)),
29
+                ('rght', models.PositiveIntegerField(editable=False)),
30
+                ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
31
+                ('level', models.PositiveIntegerField(editable=False)),
32
+                ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
33
+                ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='pos.uimenu')),
34
+            ],
35
+            options={
36
+                'abstract': False,
37
+            },
38
+        ),
39
+    ]

+ 22 - 0
app/pos/migrations/0020_setting_note.py

@@ -0,0 +1,22 @@
1
+# Generated by Django 3.2.13 on 2022-05-21 04:43
2
+
3
+from django.db import migrations, models
4
+from django.contrib.postgres.operations import HStoreExtension, UnaccentExtension
5
+
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('pos', '0019_uimenu'),
12
+    ]
13
+
14
+    operations = [
15
+        HStoreExtension(),
16
+        UnaccentExtension(),
17
+        migrations.AddField(
18
+            model_name='setting',
19
+            name='note',
20
+            field=models.CharField(blank=True, max_length=200, null=True),
21
+        ),
22
+    ]

+ 19 - 0
app/pos/migrations/0021_alter_setting_data.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-05-21 04:45
2
+
3
+import django.contrib.postgres.fields.hstore
4
+from django.db import migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0020_setting_note'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AlterField(
15
+            model_name='setting',
16
+            name='data',
17
+            field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, null=True),
18
+        ),
19
+    ]

+ 18 - 0
app/pos/migrations/0022_alter_uimenu_url.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-05-21 05:21
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0021_alter_setting_data'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterField(
14
+            model_name='uimenu',
15
+            name='url',
16
+            field=models.CharField(max_length=200),
17
+        ),
18
+    ]

+ 18 - 0
app/pos/migrations/0023_alter_store_name.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-05-21 05:27
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0022_alter_uimenu_url'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterField(
14
+            model_name='store',
15
+            name='name',
16
+            field=models.CharField(default='Unititled', max_length=100),
17
+        ),
18
+    ]

+ 19 - 0
app/pos/migrations/0024_uimenu_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-05-21 07:33
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0023_alter_store_name'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='uimenu',
16
+            name='store',
17
+            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='pos.store'),
18
+        ),
19
+    ]

+ 18 - 0
app/pos/migrations/0025_uimenu_icon.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-05-22 16:34
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0024_uimenu_store'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='uimenu',
15
+            name='icon',
16
+            field=models.CharField(blank=True, default='file', max_length=100, null=True),
17
+        ),
18
+    ]

+ 19 - 0
app/pos/migrations/0026_order_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-05-23 05:24
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0025_uimenu_icon'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='order',
16
+            name='store',
17
+            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pos.store'),
18
+        ),
19
+    ]

+ 19 - 0
app/pos/migrations/0027_orderitem_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-05-24 04:51
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0026_order_store'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='orderitem',
16
+            name='store',
17
+            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pos.store'),
18
+        ),
19
+    ]

+ 28 - 0
app/pos/migrations/0028_auto_20220529_1350.py

@@ -0,0 +1,28 @@
1
+# Generated by Django 3.2.13 on 2022-05-29 06:50
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0027_orderitem_store'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='store',
15
+            name='address',
16
+            field=models.TextField(blank=True, null=True),
17
+        ),
18
+        migrations.AddField(
19
+            model_name='store',
20
+            name='province',
21
+            field=models.CharField(blank=True, max_length=100, null=True),
22
+        ),
23
+        migrations.AddField(
24
+            model_name='store',
25
+            name='storeType',
26
+            field=models.CharField(blank=True, max_length=100, null=True),
27
+        ),
28
+    ]

+ 40 - 0
app/pos/migrations/0029_auto_20220530_1225.py

@@ -0,0 +1,40 @@
1
+# Generated by Django 3.2.13 on 2022-05-30 05:25
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+import taggit.managers
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('taggit', '0005_auto_20220424_2025'),
12
+        ('pos', '0028_auto_20220529_1350'),
13
+    ]
14
+
15
+    operations = [
16
+        migrations.RemoveField(
17
+            model_name='menufile',
18
+            name='store',
19
+        ),
20
+        migrations.AddField(
21
+            model_name='menufile',
22
+            name='menuItem',
23
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pos.menuitem'),
24
+        ),
25
+        migrations.AlterField(
26
+            model_name='menufile',
27
+            name='tags',
28
+            field=taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
29
+        ),
30
+        migrations.AlterField(
31
+            model_name='store',
32
+            name='storeType',
33
+            field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Store Type'),
34
+        ),
35
+        migrations.AlterField(
36
+            model_name='storefile',
37
+            name='tags',
38
+            field=taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
39
+        ),
40
+    ]

+ 19 - 0
app/pos/migrations/0030_bookingtable_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-05-30 07:10
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0029_auto_20220530_1225'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='bookingtable',
16
+            name='store',
17
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pos.store'),
18
+        ),
19
+    ]

+ 23 - 0
app/pos/migrations/0031_auto_20220530_1417.py

@@ -0,0 +1,23 @@
1
+# Generated by Django 3.2.13 on 2022-05-30 07:17
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0030_bookingtable_store'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='bookingtable',
15
+            name='onTime',
16
+            field=models.TimeField(null=True),
17
+        ),
18
+        migrations.AlterField(
19
+            model_name='bookingtable',
20
+            name='onDate',
21
+            field=models.DateField(null=True),
22
+        ),
23
+    ]

+ 135 - 0
app/pos/migrations/0032_auto_20220530_1510.py

@@ -0,0 +1,135 @@
1
+# Generated by Django 3.2.13 on 2022-05-30 08:10
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+import djrichtextfield.models
7
+import mptt.fields
8
+
9
+
10
+class Migration(migrations.Migration):
11
+
12
+    dependencies = [
13
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14
+        ('pos', '0031_auto_20220530_1417'),
15
+    ]
16
+
17
+    operations = [
18
+        migrations.RemoveField(
19
+            model_name='bookingtable',
20
+            name='inProcess',
21
+        ),
22
+        migrations.RemoveField(
23
+            model_name='bookingtable',
24
+            name='status',
25
+        ),
26
+        migrations.RemoveField(
27
+            model_name='ingredient',
28
+            name='inProcess',
29
+        ),
30
+        migrations.RemoveField(
31
+            model_name='ingredient',
32
+            name='status',
33
+        ),
34
+        migrations.RemoveField(
35
+            model_name='menufile',
36
+            name='inProcess',
37
+        ),
38
+        migrations.RemoveField(
39
+            model_name='menufile',
40
+            name='status',
41
+        ),
42
+        migrations.RemoveField(
43
+            model_name='menuitem',
44
+            name='inProcess',
45
+        ),
46
+        migrations.RemoveField(
47
+            model_name='menuitem',
48
+            name='status',
49
+        ),
50
+        migrations.RemoveField(
51
+            model_name='order',
52
+            name='inProcess',
53
+        ),
54
+        migrations.RemoveField(
55
+            model_name='order',
56
+            name='status',
57
+        ),
58
+        migrations.RemoveField(
59
+            model_name='orderitem',
60
+            name='inProcess',
61
+        ),
62
+        migrations.RemoveField(
63
+            model_name='orderitem',
64
+            name='status',
65
+        ),
66
+        migrations.RemoveField(
67
+            model_name='setting',
68
+            name='inProcess',
69
+        ),
70
+        migrations.RemoveField(
71
+            model_name='setting',
72
+            name='status',
73
+        ),
74
+        migrations.RemoveField(
75
+            model_name='store',
76
+            name='inProcess',
77
+        ),
78
+        migrations.RemoveField(
79
+            model_name='store',
80
+            name='status',
81
+        ),
82
+        migrations.RemoveField(
83
+            model_name='storefile',
84
+            name='inProcess',
85
+        ),
86
+        migrations.RemoveField(
87
+            model_name='storefile',
88
+            name='status',
89
+        ),
90
+        migrations.RemoveField(
91
+            model_name='table',
92
+            name='inProcess',
93
+        ),
94
+        migrations.RemoveField(
95
+            model_name='table',
96
+            name='status',
97
+        ),
98
+        migrations.RemoveField(
99
+            model_name='uimenu',
100
+            name='inProcess',
101
+        ),
102
+        migrations.RemoveField(
103
+            model_name='uimenu',
104
+            name='status',
105
+        ),
106
+        migrations.CreateModel(
107
+            name='PostCategory',
108
+            fields=[
109
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
110
+                ('name', models.CharField(max_length=200)),
111
+                ('lft', models.PositiveIntegerField(editable=False)),
112
+                ('rght', models.PositiveIntegerField(editable=False)),
113
+                ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
114
+                ('level', models.PositiveIntegerField(editable=False)),
115
+                ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='pos.postcategory')),
116
+            ],
117
+            options={
118
+                'abstract': False,
119
+            },
120
+        ),
121
+        migrations.CreateModel(
122
+            name='Post',
123
+            fields=[
124
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
125
+                ('created_at', models.DateTimeField(auto_now_add=True)),
126
+                ('updated_at', models.DateTimeField(auto_now=True)),
127
+                ('title', models.CharField(max_length=200)),
128
+                ('content', djrichtextfield.models.RichTextField()),
129
+                ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
130
+            ],
131
+            options={
132
+                'abstract': False,
133
+            },
134
+        ),
135
+    ]

+ 19 - 0
app/pos/migrations/0033_post_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-05-30 08:29
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0032_auto_20220530_1510'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='post',
16
+            name='store',
17
+            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='pos.store'),
18
+        ),
19
+    ]

+ 31 - 0
app/pos/migrations/0034_auto_20220531_2134.py

@@ -0,0 +1,31 @@
1
+# Generated by Django 3.2.13 on 2022-05-31 14:34
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+import djrichtextfield.models
6
+import mptt.fields
7
+
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    dependencies = [
12
+        ('pos', '0033_post_store'),
13
+    ]
14
+
15
+    operations = [
16
+        migrations.AddField(
17
+            model_name='post',
18
+            name='category',
19
+            field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pos.postcategory'),
20
+        ),
21
+        migrations.AddField(
22
+            model_name='post',
23
+            name='featureImage',
24
+            field=models.ImageField(blank=True, height_field=800, null=True, upload_to='uploads/%Y/%m/%d/', width_field=450),
25
+        ),
26
+        migrations.AlterField(
27
+            model_name='post',
28
+            name='content',
29
+            field=djrichtextfield.models.RichTextField(blank=True),
30
+        ),
31
+    ]

+ 23 - 0
app/pos/migrations/0035_auto_20220531_2151.py

@@ -0,0 +1,23 @@
1
+# Generated by Django 3.2.13 on 2022-05-31 14:51
2
+
3
+from django.db import migrations
4
+import mptt.fields
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0034_auto_20220531_2134'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.RemoveField(
15
+            model_name='post',
16
+            name='category',
17
+        ),
18
+        migrations.AddField(
19
+            model_name='post',
20
+            name='category',
21
+            field=mptt.fields.TreeManyToManyField(blank=True, null=True, to='pos.PostCategory'),
22
+        ),
23
+    ]

+ 18 - 0
app/pos/migrations/0036_alter_post_featureimage.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-05-31 15:01
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0035_auto_20220531_2151'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterField(
14
+            model_name='post',
15
+            name='featureImage',
16
+            field=models.ImageField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'),
17
+        ),
18
+    ]

+ 19 - 0
app/pos/migrations/0037_post_photos.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-05-31 15:06
2
+
3
+import django.contrib.postgres.fields
4
+from django.db import migrations, models
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0036_alter_post_featureimage'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='post',
16
+            name='photos',
17
+            field=django.contrib.postgres.fields.ArrayField(base_field=models.ImageField(upload_to='uploads/%Y/%m/%d/'), blank=True, null=True, size=None),
18
+        ),
19
+    ]

+ 37 - 0
app/pos/migrations/0038_auto_20220531_2208.py

@@ -0,0 +1,37 @@
1
+# Generated by Django 3.2.13 on 2022-05-31 15:08
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+import taggit.managers
7
+
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    dependencies = [
12
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+        ('taggit', '0005_auto_20220424_2025'),
14
+        ('pos', '0037_post_photos'),
15
+    ]
16
+
17
+    operations = [
18
+        migrations.RemoveField(
19
+            model_name='post',
20
+            name='photos',
21
+        ),
22
+        migrations.CreateModel(
23
+            name='PostFile',
24
+            fields=[
25
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
26
+                ('created_at', models.DateTimeField(auto_now_add=True)),
27
+                ('updated_at', models.DateTimeField(auto_now=True)),
28
+                ('file', models.FileField(upload_to='uploads/%Y/%m/%d/')),
29
+                ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
30
+                ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pos.post')),
31
+                ('tags', taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
32
+            ],
33
+            options={
34
+                'abstract': False,
35
+            },
36
+        ),
37
+    ]

+ 20 - 0
app/pos/migrations/0039_post_tags.py

@@ -0,0 +1,20 @@
1
+# Generated by Django 3.2.13 on 2022-06-01 07:23
2
+
3
+from django.db import migrations
4
+import taggit.managers
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('taggit', '0005_auto_20220424_2025'),
11
+        ('pos', '0038_auto_20220531_2208'),
12
+    ]
13
+
14
+    operations = [
15
+        migrations.AddField(
16
+            model_name='post',
17
+            name='tags',
18
+            field=taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
19
+        ),
20
+    ]

+ 18 - 0
app/pos/migrations/0040_post_description.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-01 07:36
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0039_post_tags'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='post',
15
+            name='description',
16
+            field=models.TextField(blank=True, null=True),
17
+        ),
18
+    ]

+ 32 - 0
app/pos/migrations/0041_customer.py

@@ -0,0 +1,32 @@
1
+# Generated by Django 3.2.13 on 2022-06-01 07:41
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+        ('pos', '0040_post_description'),
13
+    ]
14
+
15
+    operations = [
16
+        migrations.CreateModel(
17
+            name='Customer',
18
+            fields=[
19
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
+                ('created_at', models.DateTimeField(auto_now_add=True)),
21
+                ('updated_at', models.DateTimeField(auto_now=True)),
22
+                ('fullName', models.CharField(max_length=200)),
23
+                ('email', models.EmailField(blank=True, max_length=254, null=True)),
24
+                ('lineId', models.CharField(blank=True, max_length=100, null=True)),
25
+                ('gender', models.CharField(choices=[('na', 'N/A'), ('lgbt', 'LGBT'), ('male', 'Male'), ('female', 'Female')], default='na', max_length=40)),
26
+                ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
27
+            ],
28
+            options={
29
+                'abstract': False,
30
+            },
31
+        ),
32
+    ]

+ 19 - 0
app/pos/migrations/0042_customer_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-06-01 08:19
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0041_customer'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='customer',
16
+            name='store',
17
+            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='pos.store'),
18
+        ),
19
+    ]

+ 32 - 0
app/pos/migrations/0043_code.py

@@ -0,0 +1,32 @@
1
+# Generated by Django 3.2.13 on 2022-06-02 14:02
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+import django_editorjs_fields.fields
7
+
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    dependencies = [
12
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+        ('pos', '0042_customer_store'),
14
+    ]
15
+
16
+    operations = [
17
+        migrations.CreateModel(
18
+            name='Code',
19
+            fields=[
20
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
+                ('created_at', models.DateTimeField(auto_now_add=True)),
22
+                ('updated_at', models.DateTimeField(auto_now=True)),
23
+                ('body_default', models.TextField()),
24
+                ('body_editorjs', django_editorjs_fields.fields.EditorJsJSONField()),
25
+                ('body_editorjs_text', django_editorjs_fields.fields.EditorJsTextField()),
26
+                ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
27
+            ],
28
+            options={
29
+                'abstract': False,
30
+            },
31
+        ),
32
+    ]

+ 24 - 0
app/pos/migrations/0044_auto_20220603_1058.py

@@ -0,0 +1,24 @@
1
+# Generated by Django 3.2.13 on 2022-06-03 03:58
2
+
3
+from django.db import migrations
4
+import filebrowser.fields
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0043_code'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='code',
16
+            name='document',
17
+            field=filebrowser.fields.FileBrowseField(blank=True, max_length=200, verbose_name='PDF'),
18
+        ),
19
+        migrations.AddField(
20
+            model_name='code',
21
+            name='image',
22
+            field=filebrowser.fields.FileBrowseField(blank=True, max_length=200, verbose_name='Image'),
23
+        ),
24
+    ]

+ 18 - 0
app/pos/migrations/0045_code_code.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-03 04:35
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0044_auto_20220603_1058'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='code',
15
+            name='code',
16
+            field=models.TextField(blank=True),
17
+        ),
18
+    ]

+ 48 - 0
app/pos/migrations/0046_auto_20220603_1258.py

@@ -0,0 +1,48 @@
1
+# Generated by Django 3.2.13 on 2022-06-03 05:58
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0045_code_code'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.RenameField(
14
+            model_name='code',
15
+            old_name='code',
16
+            new_name='cssCode',
17
+        ),
18
+        migrations.RemoveField(
19
+            model_name='code',
20
+            name='body_default',
21
+        ),
22
+        migrations.RemoveField(
23
+            model_name='code',
24
+            name='body_editorjs',
25
+        ),
26
+        migrations.RemoveField(
27
+            model_name='code',
28
+            name='body_editorjs_text',
29
+        ),
30
+        migrations.RemoveField(
31
+            model_name='code',
32
+            name='document',
33
+        ),
34
+        migrations.RemoveField(
35
+            model_name='code',
36
+            name='image',
37
+        ),
38
+        migrations.AddField(
39
+            model_name='code',
40
+            name='jsCode',
41
+            field=models.TextField(blank=True),
42
+        ),
43
+        migrations.AddField(
44
+            model_name='code',
45
+            name='pluginCode',
46
+            field=models.TextField(blank=True),
47
+        ),
48
+    ]

+ 19 - 0
app/pos/migrations/0047_code_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-06-03 06:07
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0046_auto_20220603_1258'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='code',
16
+            name='store',
17
+            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='pos.store'),
18
+        ),
19
+    ]

+ 19 - 0
app/pos/migrations/0048_alter_code_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-06-03 06:15
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0047_code_store'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AlterField(
15
+            model_name='code',
16
+            name='store',
17
+            field=models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='pos.store'),
18
+        ),
19
+    ]

+ 18 - 0
app/pos/migrations/0049_uimenu_norder.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-03 08:01
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0048_alter_code_store'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='uimenu',
15
+            name='nOrder',
16
+            field=models.IntegerField(default=1),
17
+        ),
18
+    ]

+ 19 - 0
app/pos/migrations/0050_profile_store.py

@@ -0,0 +1,19 @@
1
+# Generated by Django 3.2.13 on 2022-06-03 13:47
2
+
3
+from django.db import migrations, models
4
+import django.db.models.deletion
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0049_uimenu_norder'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='profile',
16
+            name='store',
17
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pos.store'),
18
+        ),
19
+    ]

+ 18 - 0
app/pos/migrations/0051_menuitem_qrcode.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-05 17:25
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0050_profile_store'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='menuitem',
15
+            name='qrCode',
16
+            field=models.BinaryField(blank=True, null=True),
17
+        ),
18
+    ]

+ 18 - 0
app/pos/migrations/0052_alter_menuitem_qrcode.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-05 17:59
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0051_menuitem_qrcode'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterField(
14
+            model_name='menuitem',
15
+            name='qrCode',
16
+            field=models.FileField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'),
17
+        ),
18
+    ]

+ 23 - 0
app/pos/migrations/0053_auto_20220606_1058.py

@@ -0,0 +1,23 @@
1
+# Generated by Django 3.2.13 on 2022-06-06 03:58
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0052_alter_menuitem_qrcode'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='order',
15
+            name='qrCode',
16
+            field=models.FileField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'),
17
+        ),
18
+        migrations.AddField(
19
+            model_name='store',
20
+            name='qrCode',
21
+            field=models.FileField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'),
22
+        ),
23
+    ]

+ 23 - 0
app/pos/migrations/0054_auto_20220606_1336.py

@@ -0,0 +1,23 @@
1
+# Generated by Django 3.2.13 on 2022-06-06 06:36
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0053_auto_20220606_1058'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='bookingtable',
15
+            name='qrCode',
16
+            field=models.FileField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'),
17
+        ),
18
+        migrations.AddField(
19
+            model_name='table',
20
+            name='qrCode',
21
+            field=models.FileField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'),
22
+        ),
23
+    ]

+ 18 - 0
app/pos/migrations/0055_customer_qrcode.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-06 06:41
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0054_auto_20220606_1336'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='customer',
15
+            name='qrCode',
16
+            field=models.FileField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'),
17
+        ),
18
+    ]

+ 24 - 0
app/pos/migrations/0056_auto_20220606_1348.py

@@ -0,0 +1,24 @@
1
+# Generated by Django 3.2.13 on 2022-06-06 06:48
2
+
3
+from django.db import migrations
4
+import django_google_maps.fields
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('pos', '0055_customer_qrcode'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='store',
16
+            name='geolocation',
17
+            field=django_google_maps.fields.GeoLocationField(blank=True, max_length=100, null=True),
18
+        ),
19
+        migrations.AlterField(
20
+            model_name='store',
21
+            name='address',
22
+            field=django_google_maps.fields.AddressField(blank=True, max_length=100, null=True),
23
+        ),
24
+    ]

+ 18 - 0
app/pos/migrations/0057_store_addresstext.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-06 06:50
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0056_auto_20220606_1348'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='store',
15
+            name='addressText',
16
+            field=models.TextField(blank=True, null=True),
17
+        ),
18
+    ]

+ 18 - 0
app/pos/migrations/0058_post_relatemenus.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-11 13:18
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0057_store_addresstext'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='post',
15
+            name='relateMenus',
16
+            field=models.ManyToManyField(blank=True, null=True, to='pos.MenuItem'),
17
+        ),
18
+    ]

+ 18 - 0
app/pos/migrations/0059_menuitem_featureimage.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-11 16:17
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0058_post_relatemenus'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='menuitem',
15
+            name='featureImage',
16
+            field=models.ImageField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'),
17
+        ),
18
+    ]

+ 18 - 0
app/pos/migrations/0060_menuitem_description.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 3.2.13 on 2022-06-12 05:51
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('pos', '0059_menuitem_featureimage'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='menuitem',
15
+            name='description',
16
+            field=models.TextField(blank=True, null=True),
17
+        ),
18
+    ]

+ 0 - 0
app/pos/migrations/0061_store_logo.py


Някои файлове не бяха показани, защото твърде много файлове са промени

tum/whitesports - Gogs: Simplico Git Service

説明なし

class-wp-rest-plugins-controller.php 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. <?php
  2. /**
  3. * REST API: WP_REST_Plugins_Controller class
  4. *
  5. * @package WordPress
  6. * @subpackage REST_API
  7. * @since 5.5.0
  8. */
  9. /**
  10. * Core class to access plugins via the REST API.
  11. *
  12. * @since 5.5.0
  13. *
  14. * @see WP_REST_Controller
  15. */
  16. class WP_REST_Plugins_Controller extends WP_REST_Controller {
  17. const PATTERN = '[^.\/]+(?:\/[^.\/]+)?';
  18. /**
  19. * Plugins controller constructor.
  20. *
  21. * @since 5.5.0
  22. */
  23. public function __construct() {
  24. $this->namespace = 'wp/v2';
  25. $this->rest_base = 'plugins';
  26. }
  27. /**
  28. * Registers the routes for the plugins controller.
  29. *
  30. * @since 5.5.0
  31. */
  32. public function register_routes() {
  33. register_rest_route(
  34. $this->namespace,
  35. '/' . $this->rest_base,
  36. array(
  37. array(
  38. 'methods' => WP_REST_Server::READABLE,
  39. 'callback' => array( $this, 'get_items' ),
  40. 'permission_callback' => array( $this, 'get_items_permissions_check' ),
  41. 'args' => $this->get_collection_params(),
  42. ),
  43. array(
  44. 'methods' => WP_REST_Server::CREATABLE,
  45. 'callback' => array( $this, 'create_item' ),
  46. 'permission_callback' => array( $this, 'create_item_permissions_check' ),
  47. 'args' => array(
  48. 'slug' => array(
  49. 'type' => 'string',
  50. 'required' => true,
  51. 'description' => __( 'WordPress.org plugin directory slug.' ),
  52. 'pattern' => '[\w\-]+',
  53. ),
  54. 'status' => array(
  55. 'description' => __( 'The plugin activation status.' ),
  56. 'type' => 'string',
  57. 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
  58. 'default' => 'inactive',
  59. ),
  60. ),
  61. ),
  62. 'schema' => array( $this, 'get_public_item_schema' ),
  63. )
  64. );
  65. register_rest_route(
  66. $this->namespace,
  67. '/' . $this->rest_base . '/(?P<plugin>' . self::PATTERN . ')',
  68. array(
  69. array(
  70. 'methods' => WP_REST_Server::READABLE,
  71. 'callback' => array( $this, 'get_item' ),
  72. 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  73. ),
  74. array(
  75. 'methods' => WP_REST_Server::EDITABLE,
  76. 'callback' => array( $this, 'update_item' ),
  77. 'permission_callback' => array( $this, 'update_item_permissions_check' ),
  78. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  79. ),
  80. array(
  81. 'methods' => WP_REST_Server::DELETABLE,
  82. 'callback' => array( $this, 'delete_item' ),
  83. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
  84. ),
  85. 'args' => array(
  86. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  87. 'plugin' => array(
  88. 'type' => 'string',
  89. 'pattern' => self::PATTERN,
  90. 'validate_callback' => array( $this, 'validate_plugin_param' ),
  91. 'sanitize_callback' => array( $this, 'sanitize_plugin_param' ),
  92. ),
  93. ),
  94. 'schema' => array( $this, 'get_public_item_schema' ),
  95. )
  96. );
  97. }
  98. /**
  99. * Checks if a given request has access to get plugins.
  100. *
  101. * @since 5.5.0
  102. *
  103. * @param WP_REST_Request $request Full details about the request.
  104. * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  105. */
  106. public function get_items_permissions_check( $request ) {
  107. if ( ! current_user_can( 'activate_plugins' ) ) {
  108. return new WP_Error(
  109. 'rest_cannot_view_plugins',
  110. __( 'Sorry, you are not allowed to manage plugins for this site.' ),
  111. array( 'status' => rest_authorization_required_code() )
  112. );
  113. }
  114. return true;
  115. }
  116. /**
  117. * Retrieves a collection of plugins.
  118. *
  119. * @since 5.5.0
  120. *
  121. * @param WP_REST_Request $request Full details about the request.
  122. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  123. */
  124. public function get_items( $request ) {
  125. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  126. $plugins = array();
  127. foreach ( get_plugins() as $file => $data ) {
  128. if ( is_wp_error( $this->check_read_permission( $file ) ) ) {
  129. continue;
  130. }
  131. $data['_file'] = $file;
  132. if ( ! $this->does_plugin_match_request( $request, $data ) ) {
  133. continue;
  134. }
  135. $plugins[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $data, $request ) );
  136. }
  137. return new WP_REST_Response( $plugins );
  138. }
  139. /**
  140. * Checks if a given request has access to get a specific plugin.
  141. *
  142. * @since 5.5.0
  143. *
  144. * @param WP_REST_Request $request Full details about the request.
  145. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
  146. */
  147. public function get_item_permissions_check( $request ) {
  148. if ( ! current_user_can( 'activate_plugins' ) ) {
  149. return new WP_Error(
  150. 'rest_cannot_view_plugin',
  151. __( 'Sorry, you are not allowed to manage plugins for this site.' ),
  152. array( 'status' => rest_authorization_required_code() )
  153. );
  154. }
  155. $can_read = $this->check_read_permission( $request['plugin'] );
  156. if ( is_wp_error( $can_read ) ) {
  157. return $can_read;
  158. }
  159. return true;
  160. }
  161. /**
  162. * Retrieves one plugin from the site.
  163. *
  164. * @since 5.5.0
  165. *
  166. * @param WP_REST_Request $request Full details about the request.
  167. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  168. */
  169. public function get_item( $request ) {
  170. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  171. $data = $this->get_plugin_data( $request['plugin'] );
  172. if ( is_wp_error( $data ) ) {
  173. return $data;
  174. }
  175. return $this->prepare_item_for_response( $data, $request );
  176. }
  177. /**
  178. * Checks if the given plugin can be viewed by the current user.
  179. *
  180. * On multisite, this hides non-active network only plugins if the user does not have permission
  181. * to manage network plugins.
  182. *
  183. * @since 5.5.0
  184. *
  185. * @param string $plugin The plugin file to check.
  186. * @return true|WP_Error True if can read, a WP_Error instance otherwise.
  187. */
  188. protected function check_read_permission( $plugin ) {
  189. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  190. if ( ! $this->is_plugin_installed( $plugin ) ) {
  191. return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) );
  192. }
  193. if ( ! is_multisite() ) {
  194. return true;
  195. }
  196. if ( ! is_network_only_plugin( $plugin ) || is_plugin_active( $plugin ) || current_user_can( 'manage_network_plugins' ) ) {
  197. return true;
  198. }
  199. return new WP_Error(
  200. 'rest_cannot_view_plugin',
  201. __( 'Sorry, you are not allowed to manage this plugin.' ),
  202. array( 'status' => rest_authorization_required_code() )
  203. );
  204. }
  205. /**
  206. * Checks if a given request has access to upload plugins.
  207. *
  208. * @since 5.5.0
  209. *
  210. * @param WP_REST_Request $request Full details about the request.
  211. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
  212. */
  213. public function create_item_permissions_check( $request ) {
  214. if ( ! current_user_can( 'install_plugins' ) ) {
  215. return new WP_Error(
  216. 'rest_cannot_install_plugin',
  217. __( 'Sorry, you are not allowed to install plugins on this site.' ),
  218. array( 'status' => rest_authorization_required_code() )
  219. );
  220. }
  221. if ( 'inactive' !== $request['status'] && ! current_user_can( 'activate_plugins' ) ) {
  222. return new WP_Error(
  223. 'rest_cannot_activate_plugin',
  224. __( 'Sorry, you are not allowed to activate plugins.' ),
  225. array(
  226. 'status' => rest_authorization_required_code(),
  227. )
  228. );
  229. }
  230. return true;
  231. }
  232. /**
  233. * Uploads a plugin and optionally activates it.
  234. *
  235. * @since 5.5.0
  236. *
  237. * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  238. *
  239. * @param WP_REST_Request $request Full details about the request.
  240. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  241. */
  242. public function create_item( $request ) {
  243. global $wp_filesystem;
  244. require_once ABSPATH . 'wp-admin/includes/file.php';
  245. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  246. require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  247. require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
  248. $slug = $request['slug'];
  249. // Verify filesystem is accessible first.
  250. $filesystem_available = $this->is_filesystem_available();
  251. if ( is_wp_error( $filesystem_available ) ) {
  252. return $filesystem_available;
  253. }
  254. $api = plugins_api(
  255. 'plugin_information',
  256. array(
  257. 'slug' => $slug,
  258. 'fields' => array(
  259. 'sections' => false,
  260. 'language_packs' => true,
  261. ),
  262. )
  263. );
  264. if ( is_wp_error( $api ) ) {
  265. if ( false !== strpos( $api->get_error_message(), 'Plugin not found.' ) ) {
  266. $api->add_data( array( 'status' => 404 ) );
  267. } else {
  268. $api->add_data( array( 'status' => 500 ) );
  269. }
  270. return $api;
  271. }
  272. $skin = new WP_Ajax_Upgrader_Skin();
  273. $upgrader = new Plugin_Upgrader( $skin );
  274. $result = $upgrader->install( $api->download_link );
  275. if ( is_wp_error( $result ) ) {
  276. $result->add_data( array( 'status' => 500 ) );
  277. return $result;
  278. }
  279. // This should be the same as $result above.
  280. if ( is_wp_error( $skin->result ) ) {
  281. $skin->result->add_data( array( 'status' => 500 ) );
  282. return $skin->result;
  283. }
  284. if ( $skin->get_errors()->has_errors() ) {
  285. $error = $skin->get_errors();
  286. $error->add_data( array( 'status' => 500 ) );
  287. return $error;
  288. }
  289. if ( is_null( $result ) ) {
  290. // Pass through the error from WP_Filesystem if one was raised.
  291. if ( $wp_filesystem instanceof WP_Filesystem_Base
  292. && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors()
  293. ) {
  294. return new WP_Error(
  295. 'unable_to_connect_to_filesystem',
  296. $wp_filesystem->errors->get_error_message(),
  297. array( 'status' => 500 )
  298. );
  299. }
  300. return new WP_Error(
  301. 'unable_to_connect_to_filesystem',
  302. __( 'Unable to connect to the filesystem. Please confirm your credentials.' ),
  303. array( 'status' => 500 )
  304. );
  305. }
  306. $file = $upgrader->plugin_info();
  307. if ( ! $file ) {
  308. return new WP_Error(
  309. 'unable_to_determine_installed_plugin',
  310. __( 'Unable to determine what plugin was installed.' ),
  311. array( 'status' => 500 )
  312. );
  313. }
  314. if ( 'inactive' !== $request['status'] ) {
  315. $can_change_status = $this->plugin_status_permission_check( $file, $request['status'], 'inactive' );
  316. if ( is_wp_error( $can_change_status ) ) {
  317. return $can_change_status;
  318. }
  319. $changed_status = $this->handle_plugin_status( $file, $request['status'], 'inactive' );
  320. if ( is_wp_error( $changed_status ) ) {
  321. return $changed_status;
  322. }
  323. }
  324. // Install translations.
  325. $installed_locales = array_values( get_available_languages() );
  326. /** This filter is documented in wp-includes/update.php */
  327. $installed_locales = apply_filters( 'plugins_update_check_locales', $installed_locales );
  328. $language_packs = array_map(
  329. static function( $item ) {
  330. return (object) $item;
  331. },
  332. $api->language_packs
  333. );
  334. $language_packs = array_filter(
  335. $language_packs,
  336. static function( $pack ) use ( $installed_locales ) {
  337. return in_array( $pack->language, $installed_locales, true );
  338. }
  339. );
  340. if ( $language_packs ) {
  341. $lp_upgrader = new Language_Pack_Upgrader( $skin );
  342. // Install all applicable language packs for the plugin.
  343. $lp_upgrader->bulk_upgrade( $language_packs );
  344. }
  345. $path = WP_PLUGIN_DIR . '/' . $file;
  346. $data = get_plugin_data( $path, false, false );
  347. $data['_file'] = $file;
  348. $response = $this->prepare_item_for_response( $data, $request );
  349. $response->set_status( 201 );
  350. $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $file, 0, - 4 ) ) ) );
  351. return $response;
  352. }
  353. /**
  354. * Checks if a given request has access to update a specific plugin.
  355. *
  356. * @since 5.5.0
  357. *
  358. * @param WP_REST_Request $request Full details about the request.
  359. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
  360. */
  361. public function update_item_permissions_check( $request ) {
  362. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  363. if ( ! current_user_can( 'activate_plugins' ) ) {
  364. return new WP_Error(
  365. 'rest_cannot_manage_plugins',
  366. __( 'Sorry, you are not allowed to manage plugins for this site.' ),
  367. array( 'status' => rest_authorization_required_code() )
  368. );
  369. }
  370. $can_read = $this->check_read_permission( $request['plugin'] );
  371. if ( is_wp_error( $can_read ) ) {
  372. return $can_read;
  373. }
  374. $status = $this->get_plugin_status( $request['plugin'] );
  375. if ( $request['status'] && $status !== $request['status'] ) {
  376. $can_change_status = $this->plugin_status_permission_check( $request['plugin'], $request['status'], $status );
  377. if ( is_wp_error( $can_change_status ) ) {
  378. return $can_change_status;
  379. }
  380. }
  381. return true;
  382. }
  383. /**
  384. * Updates one plugin.
  385. *
  386. * @since 5.5.0
  387. *
  388. * @param WP_REST_Request $request Full details about the request.
  389. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  390. */
  391. public function update_item( $request ) {
  392. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  393. $data = $this->get_plugin_data( $request['plugin'] );
  394. if ( is_wp_error( $data ) ) {
  395. return $data;
  396. }
  397. $status = $this->get_plugin_status( $request['plugin'] );
  398. if ( $request['status'] && $status !== $request['status'] ) {
  399. $handled = $this->handle_plugin_status( $request['plugin'], $request['status'], $status );
  400. if ( is_wp_error( $handled ) ) {
  401. return $handled;
  402. }
  403. }
  404. $this->update_additional_fields_for_object( $data, $request );
  405. $request['context'] = 'edit';
  406. return $this->prepare_item_for_response( $data, $request );
  407. }
  408. /**
  409. * Checks if a given request has access to delete a specific plugin.
  410. *
  411. * @since 5.5.0
  412. *
  413. * @param WP_REST_Request $request Full details about the request.
  414. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
  415. */
  416. public function delete_item_permissions_check( $request ) {
  417. if ( ! current_user_can( 'activate_plugins' ) ) {
  418. return new WP_Error(
  419. 'rest_cannot_manage_plugins',
  420. __( 'Sorry, you are not allowed to manage plugins for this site.' ),
  421. array( 'status' => rest_authorization_required_code() )
  422. );
  423. }
  424. if ( ! current_user_can( 'delete_plugins' ) ) {
  425. return new WP_Error(
  426. 'rest_cannot_manage_plugins',
  427. __( 'Sorry, you are not allowed to delete plugins for this site.' ),
  428. array( 'status' => rest_authorization_required_code() )
  429. );
  430. }
  431. $can_read = $this->check_read_permission( $request['plugin'] );
  432. if ( is_wp_error( $can_read ) ) {
  433. return $can_read;
  434. }
  435. return true;
  436. }
  437. /**
  438. * Deletes one plugin from the site.
  439. *
  440. * @since 5.5.0
  441. *
  442. * @param WP_REST_Request $request Full details about the request.
  443. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  444. */
  445. public function delete_item( $request ) {
  446. require_once ABSPATH . 'wp-admin/includes/file.php';
  447. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  448. $data = $this->get_plugin_data( $request['plugin'] );
  449. if ( is_wp_error( $data ) ) {
  450. return $data;
  451. }
  452. if ( is_plugin_active( $request['plugin'] ) ) {
  453. return new WP_Error(
  454. 'rest_cannot_delete_active_plugin',
  455. __( 'Cannot delete an active plugin. Please deactivate it first.' ),
  456. array( 'status' => 400 )
  457. );
  458. }
  459. $filesystem_available = $this->is_filesystem_available();
  460. if ( is_wp_error( $filesystem_available ) ) {
  461. return $filesystem_available;
  462. }
  463. $prepared = $this->prepare_item_for_response( $data, $request );
  464. $deleted = delete_plugins( array( $request['plugin'] ) );
  465. if ( is_wp_error( $deleted ) ) {
  466. $deleted->add_data( array( 'status' => 500 ) );
  467. return $deleted;
  468. }
  469. return new WP_REST_Response(
  470. array(
  471. 'deleted' => true,
  472. 'previous' => $prepared->get_data(),
  473. )
  474. );
  475. }
  476. /**
  477. * Prepares the plugin for the REST response.
  478. *
  479. * @since 5.5.0
  480. *
  481. * @param mixed $item Unmarked up and untranslated plugin data from {@see get_plugin_data()}.
  482. * @param WP_REST_Request $request Request object.
  483. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  484. */
  485. public function prepare_item_for_response( $item, $request ) {
  486. $item = _get_plugin_data_markup_translate( $item['_file'], $item, false );
  487. $marked = _get_plugin_data_markup_translate( $item['_file'], $item, true );
  488. $data = array(
  489. 'plugin' => substr( $item['_file'], 0, - 4 ),
  490. 'status' => $this->get_plugin_status( $item['_file'] ),
  491. 'name' => $item['Name'],
  492. 'plugin_uri' => $item['PluginURI'],
  493. 'author' => $item['Author'],
  494. 'author_uri' => $item['AuthorURI'],
  495. 'description' => array(
  496. 'raw' => $item['Description'],
  497. 'rendered' => $marked['Description'],
  498. ),
  499. 'version' => $item['Version'],
  500. 'network_only' => $item['Network'],
  501. 'requires_wp' => $item['RequiresWP'],
  502. 'requires_php' => $item['RequiresPHP'],
  503. 'textdomain' => $item['TextDomain'],
  504. );
  505. $data = $this->add_additional_fields_to_object( $data, $request );
  506. $response = new WP_REST_Response( $data );
  507. $response->add_links( $this->prepare_links( $item ) );
  508. /**
  509. * Filters plugin data for a REST API response.
  510. *
  511. * @since 5.5.0
  512. *
  513. * @param WP_REST_Response $response The response object.
  514. * @param array $item The plugin item from {@see get_plugin_data()}.
  515. * @param WP_REST_Request $request The request object.
  516. */
  517. return apply_filters( 'rest_prepare_plugin', $response, $item, $request );
  518. }
  519. /**
  520. * Prepares links for the request.
  521. *
  522. * @since 5.5.0
  523. *
  524. * @param array $item The plugin item.
  525. * @return array[]
  526. */
  527. protected function prepare_links( $item ) {
  528. return array(
  529. 'self' => array(
  530. 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $item['_file'], 0, - 4 ) ) ),
  531. ),
  532. );
  533. }
  534. /**
  535. * Gets the plugin header data for a plugin.
  536. *
  537. * @since 5.5.0
  538. *
  539. * @param string $plugin The plugin file to get data for.
  540. * @return array|WP_Error The plugin data, or a WP_Error if the plugin is not installed.
  541. */
  542. protected function get_plugin_data( $plugin ) {
  543. $plugins = get_plugins();
  544. if ( ! isset( $plugins[ $plugin ] ) ) {
  545. return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) );
  546. }
  547. $data = $plugins[ $plugin ];
  548. $data['_file'] = $plugin;
  549. return $data;
  550. }
  551. /**
  552. * Get's the activation status for a plugin.
  553. *
  554. * @since 5.5.0
  555. *
  556. * @param string $plugin The plugin file to check.
  557. * @return string Either 'network-active', 'active' or 'inactive'.
  558. */
  559. protected function get_plugin_status( $plugin ) {
  560. if ( is_plugin_active_for_network( $plugin ) ) {
  561. return 'network-active';
  562. }
  563. if ( is_plugin_active( $plugin ) ) {
  564. return 'active';
  565. }
  566. return 'inactive';
  567. }
  568. /**
  569. * Handle updating a plugin's status.
  570. *
  571. * @since 5.5.0
  572. *
  573. * @param string $plugin The plugin file to update.
  574. * @param string $new_status The plugin's new status.
  575. * @param string $current_status The plugin's current status.
  576. * @return true|WP_Error
  577. */
  578. protected function plugin_status_permission_check( $plugin, $new_status, $current_status ) {
  579. if ( is_multisite() && ( 'network-active' === $current_status || 'network-active' === $new_status ) && ! current_user_can( 'manage_network_plugins' ) ) {
  580. return new WP_Error(
  581. 'rest_cannot_manage_network_plugins',
  582. __( 'Sorry, you are not allowed to manage network plugins.' ),
  583. array( 'status' => rest_authorization_required_code() )
  584. );
  585. }
  586. if ( ( 'active' === $new_status || 'network-active' === $new_status ) && ! current_user_can( 'activate_plugin', $plugin ) ) {
  587. return new WP_Error(
  588. 'rest_cannot_activate_plugin',
  589. __( 'Sorry, you are not allowed to activate this plugin.' ),
  590. array( 'status' => rest_authorization_required_code() )
  591. );
  592. }
  593. if ( 'inactive' === $new_status && ! current_user_can( 'deactivate_plugin', $plugin ) ) {
  594. return new WP_Error(
  595. 'rest_cannot_deactivate_plugin',
  596. __( 'Sorry, you are not allowed to deactivate this plugin.' ),
  597. array( 'status' => rest_authorization_required_code() )
  598. );
  599. }
  600. return true;
  601. }
  602. /**
  603. * Handle updating a plugin's status.
  604. *
  605. * @since 5.5.0
  606. *
  607. * @param string $plugin The plugin file to update.
  608. * @param string $new_status The plugin's new status.
  609. * @param string $current_status The plugin's current status.
  610. * @return true|WP_Error
  611. */
  612. protected function handle_plugin_status( $plugin, $new_status, $current_status ) {
  613. if ( 'inactive' === $new_status ) {
  614. deactivate_plugins( $plugin, false, 'network-active' === $current_status );
  615. return true;
  616. }
  617. if ( 'active' === $new_status && 'network-active' === $current_status ) {
  618. return true;
  619. }
  620. $network_activate = 'network-active' === $new_status;
  621. if ( is_multisite() && ! $network_activate && is_network_only_plugin( $plugin ) ) {
  622. return new WP_Error(
  623. 'rest_network_only_plugin',
  624. __( 'Network only plugin must be network activated.' ),
  625. array( 'status' => 400 )
  626. );
  627. }
  628. $activated = activate_plugin( $plugin, '', $network_activate );
  629. if ( is_wp_error( $activated ) ) {
  630. $activated->add_data( array( 'status' => 500 ) );
  631. return $activated;
  632. }
  633. return true;
  634. }
  635. /**
  636. * Checks that the "plugin" parameter is a valid path.
  637. *
  638. * @since 5.5.0
  639. *
  640. * @param string $file The plugin file parameter.
  641. * @return bool
  642. */
  643. public function validate_plugin_param( $file ) {
  644. if ( ! is_string( $file ) || ! preg_match( '/' . self::PATTERN . '/u', $file ) ) {
  645. return false;
  646. }
  647. $validated = validate_file( plugin_basename( $file ) );
  648. return 0 === $validated;
  649. }
  650. /**
  651. * Sanitizes the "plugin" parameter to be a proper plugin file with ".php" appended.
  652. *
  653. * @since 5.5.0
  654. *
  655. * @param string $file The plugin file parameter.
  656. * @return string
  657. */
  658. public function sanitize_plugin_param( $file ) {
  659. return plugin_basename( sanitize_text_field( $file . '.php' ) );
  660. }
  661. /**
  662. * Checks if the plugin matches the requested parameters.
  663. *
  664. * @since 5.5.0
  665. *
  666. * @param WP_REST_Request $request The request to require the plugin matches against.
  667. * @param array $item The plugin item.
  668. * @return bool
  669. */
  670. protected function does_plugin_match_request( $request, $item ) {
  671. $search = $request['search'];
  672. if ( $search ) {
  673. $matched_search = false;
  674. foreach ( $item as $field ) {
  675. if ( is_string( $field ) && false !== strpos( strip_tags( $field ), $search ) ) {
  676. $matched_search = true;
  677. break;
  678. }
  679. }
  680. if ( ! $matched_search ) {
  681. return false;
  682. }
  683. }
  684. $status = $request['status'];
  685. if ( $status && ! in_array( $this->get_plugin_status( $item['_file'] ), $status, true ) ) {
  686. return false;
  687. }
  688. return true;
  689. }
  690. /**
  691. * Checks if the plugin is installed.
  692. *
  693. * @since 5.5.0
  694. *
  695. * @param string $plugin The plugin file.
  696. * @return bool
  697. */
  698. protected function is_plugin_installed( $plugin ) {
  699. return file_exists( WP_PLUGIN_DIR . '/' . $plugin );
  700. }
  701. /**
  702. * Determine if the endpoints are available.
  703. *
  704. * Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present.
  705. *
  706. * @since 5.5.0
  707. *
  708. * @return true|WP_Error True if filesystem is available, WP_Error otherwise.
  709. */
  710. protected function is_filesystem_available() {
  711. $filesystem_method = get_filesystem_method();
  712. if ( 'direct' === $filesystem_method ) {
  713. return true;
  714. }
  715. ob_start();
  716. $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
  717. ob_end_clean();
  718. if ( $filesystem_credentials_are_stored ) {
  719. return true;
  720. }
  721. return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.' ), array( 'status' => 500 ) );
  722. }
  723. /**
  724. * Retrieves the plugin's schema, conforming to JSON Schema.
  725. *
  726. * @since 5.5.0
  727. *
  728. * @return array Item schema data.
  729. */
  730. public function get_item_schema() {
  731. if ( $this->schema ) {
  732. return $this->add_additional_fields_schema( $this->schema );
  733. }
  734. $this->schema = array(
  735. '$schema' => 'http://json-schema.org/draft-04/schema#',
  736. 'title' => 'plugin',
  737. 'type' => 'object',
  738. 'properties' => array(
  739. 'plugin' => array(
  740. 'description' => __( 'The plugin file.' ),
  741. 'type' => 'string',
  742. 'pattern' => self::PATTERN,
  743. 'readonly' => true,
  744. 'context' => array( 'view', 'edit', 'embed' ),
  745. ),
  746. 'status' => array(
  747. 'description' => __( 'The plugin activation status.' ),
  748. 'type' => 'string',
  749. 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
  750. 'context' => array( 'view', 'edit', 'embed' ),
  751. ),
  752. 'name' => array(
  753. 'description' => __( 'The plugin name.' ),
  754. 'type' => 'string',
  755. 'readonly' => true,
  756. 'context' => array( 'view', 'edit', 'embed' ),
  757. ),
  758. 'plugin_uri' => array(
  759. 'description' => __( 'The plugin\'s website address.' ),
  760. 'type' => 'string',
  761. 'format' => 'uri',
  762. 'readonly' => true,
  763. 'context' => array( 'view', 'edit' ),
  764. ),
  765. 'author' => array(
  766. 'description' => __( 'The plugin author.' ),
  767. 'type' => 'object',
  768. 'readonly' => true,
  769. 'context' => array( 'view', 'edit' ),
  770. ),
  771. 'author_uri' => array(
  772. 'description' => __( 'Plugin author\'s website address.' ),
  773. 'type' => 'string',
  774. 'format' => 'uri',
  775. 'readonly' => true,
  776. 'context' => array( 'view', 'edit' ),
  777. ),
  778. 'description' => array(
  779. 'description' => __( 'The plugin description.' ),
  780. 'type' => 'object',
  781. 'readonly' => true,
  782. 'context' => array( 'view', 'edit' ),
  783. 'properties' => array(
  784. 'raw' => array(
  785. 'description' => __( 'The raw plugin description.' ),
  786. 'type' => 'string',
  787. ),
  788. 'rendered' => array(
  789. 'description' => __( 'The plugin description formatted for display.' ),
  790. 'type' => 'string',
  791. ),
  792. ),
  793. ),
  794. 'version' => array(
  795. 'description' => __( 'The plugin version number.' ),
  796. 'type' => 'string',
  797. 'readonly' => true,
  798. 'context' => array( 'view', 'edit' ),
  799. ),
  800. 'network_only' => array(
  801. 'description' => __( 'Whether the plugin can only be activated network-wide.' ),
  802. 'type' => 'boolean',
  803. 'readonly' => true,
  804. 'context' => array( 'view', 'edit', 'embed' ),
  805. ),
  806. 'requires_wp' => array(
  807. 'description' => __( 'Minimum required version of WordPress.' ),
  808. 'type' => 'string',
  809. 'readonly' => true,
  810. 'context' => array( 'view', 'edit', 'embed' ),
  811. ),
  812. 'requires_php' => array(
  813. 'description' => __( 'Minimum required version of PHP.' ),
  814. 'type' => 'string',
  815. 'readonly' => true,
  816. 'context' => array( 'view', 'edit', 'embed' ),
  817. ),
  818. 'textdomain' => array(
  819. 'description' => __( 'The plugin\'s text domain.' ),
  820. 'type' => 'string',
  821. 'readonly' => true,
  822. 'context' => array( 'view', 'edit' ),
  823. ),
  824. ),
  825. );
  826. return $this->add_additional_fields_schema( $this->schema );
  827. }
  828. /**
  829. * Retrieves the query params for the collections.
  830. *
  831. * @since 5.5.0
  832. *
  833. * @return array Query parameters for the collection.
  834. */
  835. public function get_collection_params() {
  836. $query_params = parent::get_collection_params();
  837. $query_params['context']['default'] = 'view';
  838. $query_params['status'] = array(
  839. 'description' => __( 'Limits results to plugins with the given status.' ),
  840. 'type' => 'array',
  841. 'items' => array(
  842. 'type' => 'string',
  843. 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
  844. ),
  845. );
  846. unset( $query_params['page'], $query_params['per_page'] );
  847. return $query_params;
  848. }
  849. }