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.
pull/1/head
Dariusz Niemczyk 2023-07-10 19:40:15 +02:00
parent 659f04ce9c
commit 45ad9bf88c
No known key found for this signature in database
GPG Key ID: 28DFE7164F497CB6
20 changed files with 490 additions and 347 deletions

View File

@ -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

View File

@ -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",

View File

@ -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;
}
});

View File

@ -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;
}
});

View File

@ -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):

View File

@ -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(

View File

@ -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()),
],
),
]

View File

@ -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(

View File

@ -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",

View File

@ -12,6 +12,6 @@ class Migration(migrations.Migration):
operations = [
DeleteTreeTrigger("storage.Item"),
CreateTreeTrigger("storage.Item", order_by=("name",)),
CreateTreeTrigger("storage.Item"),
RebuildPaths("storage.Item"),
]

View File

@ -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),
),
]

View File

@ -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(),
]

View File

@ -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"),
]

View File

@ -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"),),

View File

@ -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"
)

View File

@ -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 %}

View File

@ -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):

View File

@ -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)

View File

@ -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>

View File

@ -66,5 +66,7 @@
</script>
<script>
django.jQuery(function() { initDjangoHStoreWidget('{{ field_name }}') });
window.addEventListener("load", function (event) {
django.jQuery(function() { initDjangoHStoreWidget('{{ field_name }}') });
});
</script>