forked from wiktor/spejstore-new
Migrate old django to the newest version
Django 1.x is no longer supported, and the app needed migration to 4.x A lot of libraries has been unsupported or removed, so there's a few of unrelated changes, but necessary for the migration process to work.
This commit is contained in:
parent
659f04ce9c
commit
45ad9bf88c
|
@ -1,16 +1,34 @@
|
|||
certifi==2017.4.17
|
||||
chardet==3.0.3
|
||||
Django==1.11.15
|
||||
git+https://github.com/djangonauts/django-hstore@61427e474cb2f4be8fdfce225d78a5330bc77eb0#egg=django-hstore
|
||||
git+https://github.com/d42/django-tree@687c01c02d91cada9ca1912e34e482da9e73e27a#egg=django-tree
|
||||
django-appconf==1.0.2
|
||||
django-flat-responsive==2.0
|
||||
social-auth-app-django==2.1.0
|
||||
Django-Select2==6.3.1
|
||||
djangorestframework==3.5.4
|
||||
Pillow==3.3.1
|
||||
psycopg2==2.7.5
|
||||
djangorestframework-hstore==1.3
|
||||
requests==2.16.5
|
||||
urllib3==1.21.1
|
||||
django_markdown2==0.3.0
|
||||
asgiref==3.7.2
|
||||
certifi==2023.5.7
|
||||
cffi==1.15.1
|
||||
chardet==5.1.0
|
||||
charset-normalizer==3.2.0
|
||||
colorclass==2.2.2
|
||||
cryptography==41.0.1
|
||||
defusedxml==0.7.1
|
||||
Django==3.2.20
|
||||
django-admin-hstore-widget==1.2.1
|
||||
django-appconf==1.0.5
|
||||
django-hstore==1.4.2
|
||||
django-markdown2==0.3.1
|
||||
django-select2==8.1.2
|
||||
django-tree==0.5.6
|
||||
djangorestframework==3.14.0
|
||||
docopt==0.6.2
|
||||
idna==3.4
|
||||
markdown2==2.4.9
|
||||
oauthlib==3.2.2
|
||||
packaging==23.1
|
||||
Pillow==10.0.0
|
||||
psycopg2==2.9.6
|
||||
pycparser==2.21
|
||||
PyJWT==2.7.0
|
||||
python3-openid==3.2.0
|
||||
pytz==2023.3
|
||||
requests==2.31.0
|
||||
requests-oauthlib==1.3.1
|
||||
social-auth-app-django==5.2.0
|
||||
social-auth-core==4.4.2
|
||||
sqlparse==0.4.4
|
||||
terminaltables==3.1.10
|
||||
urllib3==2.0.3
|
||||
|
|
|
@ -32,7 +32,9 @@ SECRET_KEY = env("SECRET_KEY", "#hjthi7_udsyt*9eeyb&nwgw5x=%pk_lnz3+u2tg9@=w3p1m
|
|||
DEBUG = not PROD
|
||||
|
||||
ALLOWED_HOSTS = env(
|
||||
"ALLOWED_HOSTS", "devinventory,inventory.waw.hackerspace.pl,i,inventory"
|
||||
"ALLOWED_HOSTS",
|
||||
"devinventory,inventory.waw.hackerspace.pl,i,inventory"
|
||||
+ (",127.0.0.1" if PROD != True else ""),
|
||||
).split(",")
|
||||
LOGIN_REDIRECT_URL = "/admin/"
|
||||
|
||||
|
@ -40,7 +42,6 @@ LOGIN_REDIRECT_URL = "/admin/"
|
|||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"flat_responsive",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
|
@ -49,13 +50,13 @@ INSTALLED_APPS = [
|
|||
"django.contrib.staticfiles",
|
||||
"django.contrib.postgres",
|
||||
"social_django",
|
||||
"django_hstore",
|
||||
"tree",
|
||||
"django_select2",
|
||||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
"django_markdown2",
|
||||
"storage",
|
||||
"django_admin_hstore_widget",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -125,11 +126,6 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
},
|
||||
]
|
||||
|
||||
# select2
|
||||
|
||||
SELECT2_JS = "js/select2.min.js"
|
||||
SELECT2_CSS = "css/select2.min.css"
|
||||
SELECT2_I18N_PATH = ""
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
"auth.backend.HSWawOAuth2",
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
var initDjangoHStoreWidget = function (hstore_field_name, inline_prefix) {
|
||||
// ignore inline templates
|
||||
// if hstore_field_name contains "__prefix__"
|
||||
if (hstore_field_name.indexOf("__prefix__") > -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $ = django.jQuery;
|
||||
|
||||
// processing inlines
|
||||
if (hstore_field_name.indexOf("inline") > -1) {
|
||||
var inlineClass = $("#id_" + hstore_field_name)
|
||||
.parents(".inline-related, .grp-group")
|
||||
.attr("class");
|
||||
// if using TabularInlines stop here
|
||||
// TabularInlines not supported
|
||||
if (inlineClass.indexOf("tabular") > -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// reusable function that retrieves a template even if ID is not correct
|
||||
// (written to support inlines)
|
||||
var retrieveTemplate = function (template_name, field_name) {
|
||||
var specific_template = $("#" + template_name + "-" + field_name);
|
||||
// if found specific template return that
|
||||
if (specific_template.length) {
|
||||
return specific_template.html();
|
||||
} else {
|
||||
// get fallback template
|
||||
var html = $("." + template_name + "-inline").html();
|
||||
// replace all occurrences of __prefix__ with field_name
|
||||
// and return
|
||||
html = html.replace(/__prefix__/g, inline_prefix);
|
||||
return html;
|
||||
}
|
||||
};
|
||||
|
||||
// reusable function that compiles the UI
|
||||
var compileUI = function (params) {
|
||||
var hstore_field_id = "id_" + hstore_field_name,
|
||||
original_textarea = $("#" + hstore_field_id),
|
||||
original_value = original_textarea.val(),
|
||||
original_container = original_textarea
|
||||
.parents(".form-row, .grp-row")
|
||||
.eq(0),
|
||||
errorHtml = original_container.find(".errorlist").html(),
|
||||
json_data = {};
|
||||
|
||||
if (original_value !== "") {
|
||||
// manage case in which textarea is blank
|
||||
try {
|
||||
json_data = JSON.parse(original_value);
|
||||
} catch (e) {
|
||||
alert("invalid JSON:\n" + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var hstore_field_data = {
|
||||
id: hstore_field_id,
|
||||
label: original_container.find("label").text(),
|
||||
name: hstore_field_name,
|
||||
value: original_textarea.val(),
|
||||
help: original_container.find(".grp-help, .help").text(),
|
||||
errors: errorHtml,
|
||||
data: json_data,
|
||||
},
|
||||
// compile template
|
||||
ui_html = retrieveTemplate("hstore-ui-template", hstore_field_name),
|
||||
compiled_ui_html = _.template(ui_html, hstore_field_data);
|
||||
|
||||
// this is just to DRY up a bit
|
||||
if (params && params.replace_original === true) {
|
||||
// remove original textarea to avoid having two textareas with same ID
|
||||
original_textarea.remove();
|
||||
// inject compiled template and hide original
|
||||
original_container.after(compiled_ui_html).hide();
|
||||
}
|
||||
|
||||
return compiled_ui_html;
|
||||
};
|
||||
|
||||
// generate UI
|
||||
compileUI({ replace_original: true });
|
||||
|
||||
// cache other objects that we'll reuse
|
||||
var row_html = retrieveTemplate("hstore-row-template", hstore_field_name),
|
||||
empty_row = _.template(row_html, { key: "", value: "" }),
|
||||
$hstore = $("#id_" + hstore_field_name).parents(".hstore");
|
||||
|
||||
// reusable function that updates the textarea value
|
||||
var updateTextarea = function (container) {
|
||||
// init empty json object
|
||||
var new_value = {},
|
||||
raw_textarea = container.find("textarea"),
|
||||
rows = container.find(".form-row, .grp-row");
|
||||
|
||||
// loop over each object and populate json
|
||||
rows.each(function () {
|
||||
var inputs = $(this).find("input"),
|
||||
key = $(this).find(".hs-key").val(),
|
||||
value = $(this).find(".hs-val").val();
|
||||
new_value[key] = value;
|
||||
});
|
||||
|
||||
// update textarea value
|
||||
$(raw_textarea).val(JSON.stringify(new_value, null, 4));
|
||||
};
|
||||
|
||||
// remove row link
|
||||
$hstore.delegate("a.remove-row", "click", function (e) {
|
||||
e.preventDefault();
|
||||
// cache container jquery object before $(this) gets removed
|
||||
$(this).parents(".form-row, .grp-row").eq(0).remove();
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
|
||||
// add row link
|
||||
$hstore.delegate("a.hs-add-row, .hs-add-row a", "click", function (e) {
|
||||
e.preventDefault();
|
||||
$hstore.find(".hstore-rows").append(empty_row);
|
||||
$(".django-select2").djangoSelect2();
|
||||
$("select").on("select2:close", function () {
|
||||
$(this).focus();
|
||||
});
|
||||
});
|
||||
|
||||
// toggle textarea link
|
||||
$hstore.delegate(".hstore-toggle-txtarea", "click", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var raw_textarea = $hstore.find(".hstore-textarea"),
|
||||
hstore_rows = $hstore.find(".hstore-rows"),
|
||||
add_row = $hstore.find(".hs-add-row");
|
||||
|
||||
if (raw_textarea.is(":visible")) {
|
||||
var compiled_ui = compileUI();
|
||||
|
||||
// in case of JSON error
|
||||
if (compiled_ui === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// jquery < 1.8
|
||||
try {
|
||||
var $ui = $(compiled_ui);
|
||||
} catch (e) {
|
||||
// jquery >= 1.8
|
||||
var $ui = $($.parseHTML(compiled_ui));
|
||||
}
|
||||
|
||||
// update rows with only relevant content
|
||||
hstore_rows.html($ui.find(".hstore-rows").html());
|
||||
|
||||
raw_textarea.hide();
|
||||
hstore_rows.show();
|
||||
add_row.show();
|
||||
|
||||
$(".django-select2").djangoSelect2();
|
||||
} else {
|
||||
raw_textarea.show();
|
||||
hstore_rows.hide();
|
||||
add_row.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// update textarea whenever a field changes
|
||||
$hstore.delegate(".hs-val", "keyup propertychange", function () {
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
|
||||
$hstore.delegate(".hs-key", "change", function () {
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
};
|
||||
window.addEventListener("load", function () {
|
||||
// support inlines
|
||||
// bind only once
|
||||
if (django.hstoreWidgetBoundInlines === undefined) {
|
||||
var $ = django.jQuery;
|
||||
$(
|
||||
".grp-group .grp-add-handler, .inline-group .hs-add-row a, .inline-group .add-row"
|
||||
).click(function (e) {
|
||||
var hstore_original_textareas = $(this)
|
||||
.parents(".grp-group, .inline-group")
|
||||
.eq(0)
|
||||
.find(".hstore-original-textarea");
|
||||
// if module contains .hstore-original-textarea
|
||||
if (hstore_original_textareas.length > 0) {
|
||||
// loop over each inline
|
||||
$(this)
|
||||
.parents(".grp-group, .inline-group")
|
||||
.find(".grp-items div.grp-dynamic-form, .inline-related")
|
||||
.each(function (e, i) {
|
||||
var prefix = i;
|
||||
// loop each textarea
|
||||
$(this)
|
||||
.find(".hstore-original-textarea")
|
||||
.each(function (e, i) {
|
||||
// cache field name
|
||||
var field_name = $(this).attr("name");
|
||||
// ignore templates
|
||||
// if name attribute contains __prefix__
|
||||
if (field_name.indexOf("prefix") > -1) {
|
||||
// skip to next
|
||||
return;
|
||||
}
|
||||
initDjangoHStoreWidget(field_name, prefix);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
django.hstoreWidgetBoundInlines = true;
|
||||
}
|
||||
});
|
|
@ -1,208 +0,0 @@
|
|||
var initDjangoHStoreWidget = function(hstore_field_name, inline_prefix) {
|
||||
// ignore inline templates
|
||||
// if hstore_field_name contains "__prefix__"
|
||||
if(hstore_field_name.indexOf('__prefix__') > -1){
|
||||
return;
|
||||
}
|
||||
|
||||
// processing inlines
|
||||
if(hstore_field_name.indexOf('inline') > -1){
|
||||
var inlineClass = $('#id_'+hstore_field_name).parents('.inline-related, .grp-group').attr('class');
|
||||
// if using TabularInlines stop here
|
||||
// TabularInlines not supported
|
||||
if (inlineClass.indexOf('tabular') > -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// reusable function that retrieves a template even if ID is not correct
|
||||
// (written to support inlines)
|
||||
var retrieveTemplate = function(template_name, field_name){
|
||||
var specific_template = $('#'+template_name+'-'+field_name);
|
||||
// if found specific template return that
|
||||
if(specific_template.length){
|
||||
return specific_template.html();
|
||||
}
|
||||
else{
|
||||
// get fallback template
|
||||
var html = $('.'+template_name+'-inline').html();
|
||||
// replace all occurrences of __prefix__ with field_name
|
||||
// and return
|
||||
html = html.replace(/__prefix__/g, inline_prefix);
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
// reusable function that compiles the UI
|
||||
var compileUI = function(params){
|
||||
var hstore_field_id = 'id_'+hstore_field_name,
|
||||
original_textarea = $('#'+hstore_field_id),
|
||||
original_value = original_textarea.val(),
|
||||
original_container = original_textarea.parents('.form-row, .grp-row').eq(0),
|
||||
errorHtml = original_container.find('.errorlist').html(),
|
||||
json_data = {};
|
||||
|
||||
if(original_value !== ''){
|
||||
// manage case in which textarea is blank
|
||||
try{
|
||||
json_data = JSON.parse(original_value);
|
||||
}
|
||||
catch(e){
|
||||
alert('invalid JSON:\n'+e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var hstore_field_data = {
|
||||
"id": hstore_field_id,
|
||||
"label": original_container.find('label').text(),
|
||||
"name": hstore_field_name,
|
||||
"value": original_textarea.val(),
|
||||
"help": original_container.find('.grp-help, .help').text(),
|
||||
"errors": errorHtml,
|
||||
"data": json_data
|
||||
},
|
||||
// compile template
|
||||
ui_html = retrieveTemplate('hstore-ui-template', hstore_field_name),
|
||||
compiled_ui_html = _.template(ui_html, hstore_field_data);
|
||||
|
||||
// this is just to DRY up a bit
|
||||
if(params && params.replace_original === true){
|
||||
// remove original textarea to avoid having two textareas with same ID
|
||||
original_textarea.remove();
|
||||
// inject compiled template and hide original
|
||||
original_container.after(compiled_ui_html).hide();
|
||||
}
|
||||
|
||||
return compiled_ui_html;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// generate UI
|
||||
compileUI({ replace_original: true });
|
||||
|
||||
// cache other objects that we'll reuse
|
||||
var row_html = retrieveTemplate('hstore-row-template', hstore_field_name),
|
||||
empty_row = _.template(row_html, { 'key': '', 'value': '' }),
|
||||
$hstore = $('#id_'+hstore_field_name).parents('.hstore');
|
||||
|
||||
// reusable function that updates the textarea value
|
||||
var updateTextarea = function(container) {
|
||||
// init empty json object
|
||||
var new_value = {},
|
||||
raw_textarea = container.find('textarea'),
|
||||
rows = container.find('.form-row, .grp-row');
|
||||
|
||||
// loop over each object and populate json
|
||||
rows.each(function() {
|
||||
var inputs = $(this).find('input'),
|
||||
key = $(this).find('.hs-key').val(),
|
||||
value = $(this).find('.hs-val').val();
|
||||
new_value[key] = value;
|
||||
});
|
||||
|
||||
// update textarea value
|
||||
$(raw_textarea).val(JSON.stringify(new_value, null, 4));
|
||||
};
|
||||
|
||||
// remove row link
|
||||
$hstore.delegate('a.remove-row', 'click', function(e) {
|
||||
e.preventDefault();
|
||||
// cache container jquery object before $(this) gets removed
|
||||
$(this).parents('.form-row, .grp-row').eq(0).remove();
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
|
||||
// add row link
|
||||
$hstore.delegate('a.hs-add-row, .hs-add-row a', 'click', function(e) {
|
||||
e.preventDefault();
|
||||
$hstore.find('.hstore-rows').append(empty_row);
|
||||
$('.django-select2').djangoSelect2()
|
||||
$('select').on( 'select2:close', function () {
|
||||
$(this).focus();
|
||||
});
|
||||
});
|
||||
|
||||
// toggle textarea link
|
||||
$hstore.delegate('.hstore-toggle-txtarea', 'click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var raw_textarea = $hstore.find('.hstore-textarea'),
|
||||
hstore_rows = $hstore.find('.hstore-rows'),
|
||||
add_row = $hstore.find('.hs-add-row');
|
||||
|
||||
if(raw_textarea.is(':visible')) {
|
||||
|
||||
var compiled_ui = compileUI();
|
||||
|
||||
// in case of JSON error
|
||||
if(compiled_ui === false){
|
||||
return;
|
||||
}
|
||||
|
||||
// jquery < 1.8
|
||||
try{
|
||||
var $ui = $(compiled_ui);
|
||||
}
|
||||
// jquery >= 1.8
|
||||
catch(e){
|
||||
var $ui = $($.parseHTML(compiled_ui));
|
||||
}
|
||||
|
||||
// update rows with only relevant content
|
||||
hstore_rows.html($ui.find('.hstore-rows').html());
|
||||
|
||||
raw_textarea.hide();
|
||||
hstore_rows.show();
|
||||
add_row.show();
|
||||
|
||||
$('.django-select2').djangoSelect2()
|
||||
}
|
||||
else{
|
||||
raw_textarea.show();
|
||||
hstore_rows.hide();
|
||||
add_row.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// update textarea whenever a field changes
|
||||
$hstore.delegate('.hs-val', 'keyup propertychange', function() {
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
|
||||
$hstore.delegate('.hs-key', 'change', function() {
|
||||
updateTextarea($hstore);
|
||||
});
|
||||
};
|
||||
|
||||
django.jQuery(window).load(function() {
|
||||
// support inlines
|
||||
// bind only once
|
||||
if(django.hstoreWidgetBoundInlines === undefined){
|
||||
var $ = django.jQuery;
|
||||
$('.grp-group .grp-add-handler, .inline-group .hs-add-row a, .inline-group .add-row').click(function(e){
|
||||
var hstore_original_textareas = $(this).parents('.grp-group, .inline-group').eq(0).find('.hstore-original-textarea');
|
||||
// if module contains .hstore-original-textarea
|
||||
if(hstore_original_textareas.length > 0){
|
||||
// loop over each inline
|
||||
$(this).parents('.grp-group, .inline-group').find('.grp-items div.grp-dynamic-form, .inline-related').each(function(e, i){
|
||||
var prefix = i;
|
||||
// loop each textarea
|
||||
$(this).find('.hstore-original-textarea').each(function(e, i){
|
||||
// cache field name
|
||||
var field_name = $(this).attr('name');
|
||||
// ignore templates
|
||||
// if name attribute contains __prefix__
|
||||
if(field_name.indexOf('prefix') > -1){
|
||||
// skip to next
|
||||
return;
|
||||
}
|
||||
initDjangoHStoreWidget(field_name, prefix);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
django.hstoreWidgetBoundInlines = true;
|
||||
}
|
||||
});
|
|
@ -1,16 +1,17 @@
|
|||
from django import forms
|
||||
from django.contrib import admin
|
||||
|
||||
from django_select2.forms import ModelSelect2Widget, Select2MultipleWidget
|
||||
from django_select2.forms import Select2MultipleWidget
|
||||
|
||||
|
||||
from .models import Item, ItemImage, Category, Label
|
||||
from .widgets import ItemSelectWidget, PropsSelectWidget
|
||||
|
||||
from .widgets import PropsSelectWidget
|
||||
|
||||
|
||||
class ModelAdminMixin(object):
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return request.user.is_authenticated()
|
||||
return request.user.is_authenticated
|
||||
|
||||
has_change_permission = has_add_permission
|
||||
has_delete_permission = has_add_permission
|
||||
|
@ -24,7 +25,6 @@ class ItemForm(forms.ModelForm):
|
|||
model = Item
|
||||
exclude = []
|
||||
widgets = {
|
||||
"parent": ItemSelectWidget(model=Item),
|
||||
"categories": Select2MultipleWidget,
|
||||
"props": PropsSelectWidget,
|
||||
}
|
||||
|
@ -45,9 +45,11 @@ class ItemAdmin(ModelAdminMixin, admin.ModelAdmin):
|
|||
form = ItemForm
|
||||
inlines = [ItemImageInline, LabelInline]
|
||||
save_on_top = True
|
||||
autocomplete_fields = ["parent"]
|
||||
search_fields = ["parent"]
|
||||
|
||||
def _name(self, obj):
|
||||
return "-" * obj.get_level() + "> " + obj.name
|
||||
return ("-" * (obj.get_level() or 0)) + "> " + obj.name
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(ItemAdmin, self).save_model(request, obj, form, change)
|
||||
|
@ -67,10 +69,6 @@ class ItemAdmin(ModelAdminMixin, admin.ModelAdmin):
|
|||
return data
|
||||
|
||||
class Media:
|
||||
js = (
|
||||
# Required by select2
|
||||
"https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js",
|
||||
)
|
||||
css = {"all": ("css/admin.css",)}
|
||||
|
||||
def response_action(self, request, queryset):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from rest_framework import viewsets, generics, filters
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.decorators import action
|
||||
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
from storage.models import Item, Label
|
||||
|
@ -32,7 +33,7 @@ class LabelViewSet(viewsets.ModelViewSet):
|
|||
queryset = Label.objects
|
||||
serializer_class = LabelSerializer
|
||||
|
||||
@detail_route(methods=["post"], permission_classes=[AllowAny])
|
||||
@action(detail=True, methods=["post"], permission_classes=[AllowAny])
|
||||
def print(self, request, pk):
|
||||
quantity = min(int(request.query_params.get("quantity", 1)), 5)
|
||||
obj = self.get_object()
|
||||
|
@ -52,7 +53,7 @@ class ItemViewSet(viewsets.ModelViewSet):
|
|||
ordering_fields = "__all__"
|
||||
|
||||
def get_queryset(self):
|
||||
return Item.get_roots()
|
||||
return Item.objects.filter_roots()
|
||||
|
||||
def get_object(self):
|
||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||
|
@ -73,7 +74,7 @@ class ItemViewSet(viewsets.ModelViewSet):
|
|||
except Label.DoesNotExist:
|
||||
raise Http404()
|
||||
|
||||
@detail_route(methods=["post"], permission_classes=[AllowAny])
|
||||
@action(detail=True, methods=["post"], permission_classes=[AllowAny])
|
||||
def print(self, request, pk):
|
||||
# todo: deduplicate
|
||||
quantity = min(int(request.query_params.get("quantity", 1)), 5)
|
||||
|
@ -82,28 +83,36 @@ class ItemViewSet(viewsets.ModelViewSet):
|
|||
obj.print()
|
||||
return obj
|
||||
|
||||
@detail_route()
|
||||
@action(
|
||||
detail=True,
|
||||
)
|
||||
def children(self, request, pk):
|
||||
item = self.get_object()
|
||||
return Response(
|
||||
self.serializer_class(item.get_children().all(), many=True).data
|
||||
)
|
||||
|
||||
@detail_route()
|
||||
@action(
|
||||
detail=True,
|
||||
)
|
||||
def ancestors(self, request, pk):
|
||||
item = self.get_object()
|
||||
return Response(
|
||||
self.serializer_class(item.get_ancestors().all(), many=True).data
|
||||
)
|
||||
|
||||
@detail_route()
|
||||
@action(
|
||||
detail=True,
|
||||
)
|
||||
def descendants(self, request, pk):
|
||||
item = self.get_object()
|
||||
return Response(
|
||||
self.serializer_class(item.get_descendants().all(), many=True).data
|
||||
)
|
||||
|
||||
@detail_route()
|
||||
@action(
|
||||
detail=True,
|
||||
)
|
||||
def siblings(self, request, pk):
|
||||
item = self.get_object()
|
||||
return Response(
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django_hstore.fields
|
||||
from django.contrib.postgres.fields import HStoreField
|
||||
from django.contrib.postgres.operations import HStoreExtension
|
||||
from django.contrib.postgres.operations import TrigramExtension
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -12,6 +14,8 @@ class Migration(migrations.Migration):
|
|||
dependencies = []
|
||||
|
||||
operations = [
|
||||
HStoreExtension(),
|
||||
TrigramExtension(),
|
||||
migrations.CreateModel(
|
||||
name="Item",
|
||||
fields=[
|
||||
|
@ -26,7 +30,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
("name", models.TextField()),
|
||||
("description", models.TextField()),
|
||||
("props", django_hstore.fields.DictionaryField()),
|
||||
("props", HStoreField()),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,7 +5,11 @@ from __future__ import unicode_literals
|
|||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_hstore.fields
|
||||
from django.contrib.postgres.fields import HStoreField
|
||||
from django.contrib.postgres.operations import HStoreExtension
|
||||
from django.contrib.postgres.operations import TrigramExtension
|
||||
|
||||
|
||||
import uuid
|
||||
|
||||
|
||||
|
@ -28,12 +32,14 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
HStoreExtension(),
|
||||
TrigramExtension(),
|
||||
migrations.CreateModel(
|
||||
name="Item",
|
||||
fields=[
|
||||
("name", models.TextField()),
|
||||
("description", models.TextField(blank=True)),
|
||||
("props", django_hstore.fields.DictionaryField()),
|
||||
("props", HStoreField()),
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_hstore.fields
|
||||
from django.contrib.postgres.fields import HStoreField
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -35,7 +35,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name="item",
|
||||
name="props",
|
||||
field=django_hstore.fields.DictionaryField(blank=True),
|
||||
field=HStoreField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="label",
|
||||
|
|
|
@ -12,6 +12,6 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
DeleteTreeTrigger("storage.Item"),
|
||||
CreateTreeTrigger("storage.Item", order_by=("name",)),
|
||||
CreateTreeTrigger("storage.Item"),
|
||||
RebuildPaths("storage.Item"),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.2.20 on 2023-07-10 17:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('storage', '0006_category_icon_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='category',
|
||||
options={'ordering': ['name'], 'verbose_name_plural': 'categories'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='item',
|
||||
name='state',
|
||||
field=models.CharField(choices=[('present', 'Present'), ('taken', 'Taken'), ('broken', 'Broken'), ('missing', 'Missing'), ('depleted', 'Depleted')], default='present', max_length=31),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.20 on 2023-07-10 22:40
|
||||
|
||||
from django.db import migrations
|
||||
from django.contrib.postgres.operations import HStoreExtension
|
||||
from django.contrib.postgres.operations import TrigramExtension
|
||||
|
||||
|
||||
# This migration is necessary for current production purposes.
|
||||
# Technically is a no-op if extensions are turned on already.
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("storage", "0007_auto_20230710_1721"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
HStoreExtension(),
|
||||
TrigramExtension(),
|
||||
]
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 3.2.20 on 2023-07-11 12:27
|
||||
|
||||
from django.db import migrations
|
||||
from django.contrib.postgres.operations import HStoreExtension
|
||||
from django.contrib.postgres.operations import TrigramExtension
|
||||
from tree.operations import (
|
||||
DeleteTreeTrigger,
|
||||
CreateTreeTrigger,
|
||||
RebuildPaths,
|
||||
)
|
||||
from tree.fields import PathField
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("storage", "0008_force_extensions_via_django"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
DeleteTreeTrigger("storage.Item"),
|
||||
migrations.RemoveField("Item", "path"),
|
||||
migrations.AddField(
|
||||
model_name="item",
|
||||
name="path",
|
||||
field=PathField(db_index=True, order_by=["name"], size=None),
|
||||
),
|
||||
CreateTreeTrigger("item"),
|
||||
RebuildPaths("item"),
|
||||
]
|
|
@ -6,9 +6,10 @@ import re
|
|||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django_hstore import hstore
|
||||
from tree.fields import PathField
|
||||
from tree.models import TreeModelMixin
|
||||
from django.contrib.postgres.fields import HStoreField
|
||||
|
||||
|
||||
import requests
|
||||
|
||||
|
@ -46,7 +47,7 @@ class Category(models.Model):
|
|||
class Item(models.Model, TreeModelMixin):
|
||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
parent = models.ForeignKey("self", null=True, blank=True)
|
||||
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE)
|
||||
path = PathField()
|
||||
|
||||
name = models.TextField()
|
||||
|
@ -54,17 +55,25 @@ class Item(models.Model, TreeModelMixin):
|
|||
description = models.TextField(blank=True, null=True)
|
||||
state = models.CharField(max_length=31, choices=STATES, default=STATES[0][0])
|
||||
categories = models.ManyToManyField(Category, blank=True)
|
||||
owner = models.ForeignKey(User, null=True, blank=True, related_name="owned_items")
|
||||
owner = models.ForeignKey(
|
||||
User,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="owned_items",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
taken_by = models.ForeignKey(
|
||||
User, null=True, blank=True, related_name="taken_items"
|
||||
User,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="taken_items",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
taken_on = models.DateTimeField(blank=True, null=True)
|
||||
taken_until = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
props = hstore.DictionaryField(blank=True)
|
||||
|
||||
objects = hstore.HStoreManager()
|
||||
props = HStoreField(blank=True)
|
||||
|
||||
def short_id(self):
|
||||
# let's just hope we never have 4 294 967 296 things :)
|
||||
|
@ -105,7 +114,7 @@ class Item(models.Model, TreeModelMixin):
|
|||
|
||||
|
||||
class ItemImage(models.Model):
|
||||
item = models.ForeignKey(Item, related_name="images")
|
||||
item = models.ForeignKey(Item, related_name="images", on_delete=models.CASCADE)
|
||||
image = models.ImageField()
|
||||
|
||||
def __str__(self):
|
||||
|
@ -114,7 +123,7 @@ class ItemImage(models.Model):
|
|||
|
||||
class Label(models.Model):
|
||||
id = models.CharField(max_length=64, primary_key=True)
|
||||
item = models.ForeignKey(Item, related_name="labels")
|
||||
item = models.ForeignKey(Item, related_name="labels", on_delete=models.CASCADE)
|
||||
style = models.CharField(
|
||||
max_length=32,
|
||||
choices=(("basic_99012_v1", "Basic Dymo 89x36mm label"),),
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
from django.contrib.auth.models import User
|
||||
from storage.models import Item, Label, Category
|
||||
from rest_framework import serializers
|
||||
from rest_framework_hstore.serializers import HStoreSerializer
|
||||
|
||||
|
||||
class ItemSerializer(HStoreSerializer):
|
||||
class ItemSerializer(serializers.HStoreField):
|
||||
categories = serializers.SlugRelatedField(
|
||||
queryset=Category.objects, many=True, slug_field="name"
|
||||
)
|
||||
|
|
|
@ -1,35 +1,29 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
|
||||
{% block submit_buttons_top %}
|
||||
{# We want add another to be default submit action #}
|
||||
<input type="submit" value="Save" class="hidden" name="_addanother" />
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block submit_buttons_bottom %}
|
||||
{# We want add another to be default submit action #}
|
||||
<input type="submit" value="Save" class="hidden" name="_addanother" />
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}{{ block.super }}
|
||||
{% extends "admin/change_form.html" %} {% block submit_buttons_top %} {# We want
|
||||
add another to be default submit action #}
|
||||
<input type="submit" value="Save" class="hidden" name="_addanother" />
|
||||
{{ block.super }} {% endblock %} {% block submit_buttons_bottom %} {# We want
|
||||
add another to be default submit action #}
|
||||
<input type="submit" value="Save" class="hidden" name="_addanother" />
|
||||
{{ block.super }} {% endblock %} {% block content %}{{ block.super }}
|
||||
<script>
|
||||
$(function() {
|
||||
function fmt (state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
var result = $('<div><div><small></small></div><b></b></div>');
|
||||
result.find('small').text(state.path.join(' → ')).css({
|
||||
'opacity': 0.6,
|
||||
'letter-spacing': -0.5
|
||||
})
|
||||
result.find('b').text(state.text)
|
||||
return result;
|
||||
};
|
||||
$('.django-select2[name=parent]').djangoSelect2({
|
||||
templateResult: fmt,
|
||||
django.jQuery(function () {
|
||||
function fmt(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
var result = django.jQuery(
|
||||
"<div><div><small></small></div><b></b></div>"
|
||||
);
|
||||
result.find("small").text(state.path.join(" → ")).css({
|
||||
opacity: 0.6,
|
||||
"letter-spacing": -0.5,
|
||||
});
|
||||
result.find("b").text(state.text);
|
||||
return result;
|
||||
}
|
||||
django.jQuery(".django-select2[name=parent]").djangoSelect2({
|
||||
templateResult: fmt,
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -59,7 +59,7 @@ def apply_smart_search(query, objects):
|
|||
|
||||
|
||||
def index(request):
|
||||
return render(request, "results.html", {"results": Item.get_roots()})
|
||||
return render(request, "results.html", {"results": Item.objects.filter_roots()})
|
||||
|
||||
|
||||
def search(request):
|
||||
|
|
|
@ -1,31 +1,39 @@
|
|||
from pkg_resources import parse_version
|
||||
|
||||
from django_select2.forms import ModelSelect2Widget, HeavySelect2Widget
|
||||
from django_hstore.forms import DictionaryFieldWidget
|
||||
from django_select2.forms import HeavySelect2Widget
|
||||
|
||||
|
||||
from django import get_version
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django import forms
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.template import Context
|
||||
from django.template.loader import get_template
|
||||
from django.contrib.admin.widgets import AdminTextareaWidget
|
||||
|
||||
|
||||
class ItemSelectWidget(ModelSelect2Widget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["data_view"] = "item-complete"
|
||||
super(ItemSelectWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
return obj.name
|
||||
from django_admin_hstore_widget.forms import HStoreFormWidget
|
||||
from django.contrib.postgres.forms import forms
|
||||
from django.templatetags.static import static
|
||||
|
||||
|
||||
class PropsSelectWidget(DictionaryFieldWidget):
|
||||
class PropsSelectWidget(HStoreFormWidget):
|
||||
@property
|
||||
def media(self):
|
||||
internal_js = [
|
||||
"vendor/jquery/jquery.js",
|
||||
"django_admin_hstore_widget/underscore-min.js",
|
||||
"django_admin_hstore_widget/django_admin_hstore_widget.js",
|
||||
]
|
||||
|
||||
js = [static("admin/js/%s" % path) for path in internal_js]
|
||||
|
||||
return forms.Media(js=js)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
# it's called "original" because it will be replaced by a copy
|
||||
|
@ -35,7 +43,7 @@ class PropsSelectWidget(DictionaryFieldWidget):
|
|||
)
|
||||
|
||||
# get default HTML from AdminTextareaWidget
|
||||
html = AdminTextareaWidget.render(self, name, value, attrs)
|
||||
html = AdminTextareaWidget.render(self, name, value, attrs, renderer)
|
||||
# prepare template context
|
||||
template_context = {
|
||||
"field_name": name,
|
||||
|
@ -46,7 +54,7 @@ class PropsSelectWidget(DictionaryFieldWidget):
|
|||
"w": w.build_attrs(base_attrs=w.attrs),
|
||||
}
|
||||
# get template object
|
||||
template = get_template("hstore_%s_widget.html" % self.admin_style)
|
||||
template = get_template("hstore_default_widget.html")
|
||||
# render additional html
|
||||
additional_html = template.render(template_context)
|
||||
|
||||
|
|
|
@ -2,53 +2,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="utf-8" />
|
||||
<title>{% if title %}{{ title }} - {% endif %}Hackerspace Storage</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}" media="screen">
|
||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}" media="screen">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{% static 'css/bootstrap.css' %}"
|
||||
media="screen"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{% static 'css/custom.css' %}"
|
||||
media="screen"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">Hackerspace Storage</a>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button
|
||||
type="button"
|
||||
class="navbar-toggle collapsed"
|
||||
data-toggle="collapse"
|
||||
data-target="#bs-example-navbar-collapse-1"
|
||||
>
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">Hackerspace Storage</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/admin/storage/item/add/">Add thing</a></li>
|
||||
<li><a href="https://wiki.hackerspace.pl/members:services:inventory">How to use</a></li>
|
||||
</ul>
|
||||
<form class="navbar-form navbar-right" role="search" action="/search">
|
||||
<div class="form-group">
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/admin/storage/item/add/">Add thing</a></li>
|
||||
<li>
|
||||
<a href="https://wiki.hackerspace.pl/members:services:inventory"
|
||||
>How to use</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="navbar-form navbar-right" role="search" action="/search">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search Hackerspace" name="q" autofocus>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary" type="submit"><i class="glyphicon glyphicon-search"></i></button>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Search Hackerspace"
|
||||
name="q"
|
||||
autofocus
|
||||
/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i class="glyphicon glyphicon-search"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<!--<h1 class="page-header">Warsaw Hackerspace <small class="hidden-sm
|
||||
<!--<h1 class="page-header">Warsaw Hackerspace <small class="hidden-sm
|
||||
hidden-xs">Enjoy your stay</small></h1>-->
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% block content %} {% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
<script src="{% static 'js/jquery.min.js' %}"></script>
|
||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -66,5 +66,7 @@
|
|||
</script>
|
||||
|
||||
<script>
|
||||
django.jQuery(function() { initDjangoHStoreWidget('{{ field_name }}') });
|
||||
window.addEventListener("load", function (event) {
|
||||
django.jQuery(function() { initDjangoHStoreWidget('{{ field_name }}') });
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue