forked from wiktor/spejstore-new
Blackify the code (autoformat)
This commit is contained in:
parent
3fdf788168
commit
659f04ce9c
|
@ -1,36 +1,35 @@
|
||||||
from social_core.backends.oauth import BaseOAuth2
|
from social_core.backends.oauth import BaseOAuth2
|
||||||
from six.moves.urllib_parse import urlencode, unquote
|
from six.moves.urllib_parse import urlencode, unquote
|
||||||
|
|
||||||
|
|
||||||
class HSWawOAuth2(BaseOAuth2):
|
class HSWawOAuth2(BaseOAuth2):
|
||||||
"""Hackerspace OAuth authentication backend"""
|
"""Hackerspace OAuth authentication backend"""
|
||||||
name = 'hswaw'
|
|
||||||
ID_KEY = 'username'
|
name = "hswaw"
|
||||||
AUTHORIZATION_URL = 'https://sso.hackerspace.pl/oauth/authorize'
|
ID_KEY = "username"
|
||||||
ACCESS_TOKEN_URL = 'https://sso.hackerspace.pl/oauth/token'
|
AUTHORIZATION_URL = "https://sso.hackerspace.pl/oauth/authorize"
|
||||||
DEFAULT_SCOPE = ['profile:read']
|
ACCESS_TOKEN_URL = "https://sso.hackerspace.pl/oauth/token"
|
||||||
|
DEFAULT_SCOPE = ["profile:read"]
|
||||||
REDIRECT_STATE = False
|
REDIRECT_STATE = False
|
||||||
SCOPE_SEPARATOR = ','
|
SCOPE_SEPARATOR = ","
|
||||||
EXTRA_DATA = [
|
EXTRA_DATA = [("expires", "expires_in")]
|
||||||
('expires', 'expires_in')
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_user_details(self, response):
|
def get_user_details(self, response):
|
||||||
"""Return user details from Hackerspace account"""
|
"""Return user details from Hackerspace account"""
|
||||||
personal_email = None
|
personal_email = None
|
||||||
if response.get('personal_email'):
|
if response.get("personal_email"):
|
||||||
personal_email = response.get('personal_email')[0]
|
personal_email = response.get("personal_email")[0]
|
||||||
|
|
||||||
return {'username': response.get('username'),
|
return {
|
||||||
'email': response.get('email'),
|
"username": response.get("username"),
|
||||||
'personal_email': personal_email,
|
"email": response.get("email"),
|
||||||
}
|
"personal_email": personal_email,
|
||||||
|
}
|
||||||
|
|
||||||
def user_data(self, access_token, *args, **kwargs):
|
def user_data(self, access_token, *args, **kwargs):
|
||||||
"""Loads user data from service"""
|
"""Loads user data from service"""
|
||||||
url = 'https://sso.hackerspace.pl/api/1/profile'
|
url = "https://sso.hackerspace.pl/api/1/profile"
|
||||||
headers = {
|
headers = {"Authorization": "Bearer {}".format(access_token)}
|
||||||
'Authorization': 'Bearer {}'.format(access_token)
|
|
||||||
}
|
|
||||||
return self.get_json(url, headers=headers)
|
return self.get_json(url, headers=headers)
|
||||||
|
|
||||||
def auth_url(self):
|
def auth_url(self):
|
||||||
|
@ -40,4 +39,4 @@ class HSWawOAuth2(BaseOAuth2):
|
||||||
params.update(self.get_scope_argument())
|
params.update(self.get_scope_argument())
|
||||||
params.update(self.auth_extra_arguments())
|
params.update(self.auth_extra_arguments())
|
||||||
params = urlencode(params)
|
params = urlencode(params)
|
||||||
return '{0}?{1}'.format(self.authorization_url(), params)
|
return "{0}?{1}".format(self.authorization_url(), params)
|
||||||
|
|
|
@ -5,13 +5,19 @@ from django.contrib.auth.models import Group
|
||||||
def staff_me_up(backend, details, response, uid, user, *args, **kwargs):
|
def staff_me_up(backend, details, response, uid, user, *args, **kwargs):
|
||||||
user.is_staff = True
|
user.is_staff = True
|
||||||
try:
|
try:
|
||||||
user.groups.set([Group.objects.get(name='member')])
|
user.groups.set([Group.objects.get(name="member")])
|
||||||
except Group.DoesNotExist:
|
except Group.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
def associate_by_personal_email(backend, details, user=None, *args, **kwargs):
|
def associate_by_personal_email(backend, details, user=None, *args, **kwargs):
|
||||||
return associate_by_email(backend, {
|
return associate_by_email(
|
||||||
'email': details.get('personal_email'),
|
backend,
|
||||||
}, user, *args, **kwargs)
|
{
|
||||||
|
"email": details.get("personal_email"),
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
|
|
||||||
def auth_redirect(request):
|
def auth_redirect(request):
|
||||||
return redirect('social:begin', 'hswaw')
|
return redirect("social:begin", "hswaw")
|
||||||
|
|
|
@ -12,97 +12,97 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
def env(name, default=None):
|
def env(name, default=None):
|
||||||
return os.getenv('SPEJSTORE_' + name, default)
|
return os.getenv("SPEJSTORE_" + name, default)
|
||||||
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'build_static')
|
STATIC_ROOT = os.path.join(BASE_DIR, "build_static")
|
||||||
PROD = os.getenv('SPEJSTORE_ENV') == 'prod'
|
PROD = os.getenv("SPEJSTORE_ENV") == "prod"
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = env('SECRET_KEY', '#hjthi7_udsyt*9eeyb&nwgw5x=%pk_lnz3+u2tg9@=w3p1m*k')
|
SECRET_KEY = env("SECRET_KEY", "#hjthi7_udsyt*9eeyb&nwgw5x=%pk_lnz3+u2tg9@=w3p1m*k")
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = not PROD
|
DEBUG = not PROD
|
||||||
|
|
||||||
ALLOWED_HOSTS = env('ALLOWED_HOSTS', 'devinventory,inventory.waw.hackerspace.pl,i,inventory').split(',')
|
ALLOWED_HOSTS = env(
|
||||||
LOGIN_REDIRECT_URL = '/admin/'
|
"ALLOWED_HOSTS", "devinventory,inventory.waw.hackerspace.pl,i,inventory"
|
||||||
|
).split(",")
|
||||||
|
LOGIN_REDIRECT_URL = "/admin/"
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'flat_responsive',
|
"flat_responsive",
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
'django.contrib.postgres',
|
"django.contrib.postgres",
|
||||||
|
"social_django",
|
||||||
'social_django',
|
"django_hstore",
|
||||||
|
"tree",
|
||||||
'django_hstore',
|
"django_select2",
|
||||||
'tree',
|
"rest_framework",
|
||||||
'django_select2',
|
"rest_framework.authtoken",
|
||||||
'rest_framework',
|
"django_markdown2",
|
||||||
'rest_framework.authtoken',
|
"storage",
|
||||||
'django_markdown2',
|
|
||||||
|
|
||||||
'storage',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
'social_django.middleware.SocialAuthExceptionMiddleware',
|
"social_django.middleware.SocialAuthExceptionMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'spejstore.urls'
|
ROOT_URLCONF = "spejstore.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': ['templates/'],
|
"DIRS": ["templates/"],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
'social_django.context_processors.backends',
|
"social_django.context_processors.backends",
|
||||||
'social_django.context_processors.login_redirect',
|
"social_django.context_processors.login_redirect",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'spejstore.wsgi.application'
|
WSGI_APPLICATION = "spejstore.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': env('DB_ENGINE', 'django.db.backends.postgresql_psycopg2'),
|
"ENGINE": env("DB_ENGINE", "django.db.backends.postgresql_psycopg2"),
|
||||||
'NAME': env('DB_NAME', 'postgres'),
|
"NAME": env("DB_NAME", "postgres"),
|
||||||
'USER': env('DB_USER', 'postgres'),
|
"USER": env("DB_USER", "postgres"),
|
||||||
'PASSWORD': env('DB_PASSWORD', None),
|
"PASSWORD": env("DB_PASSWORD", None),
|
||||||
'HOST': env('DB_HOST', 'db'),
|
"HOST": env("DB_HOST", "db"),
|
||||||
'PORT': env('DB_PORT', 5432),
|
"PORT": env("DB_PORT", 5432),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,49 +112,49 @@ DATABASES = {
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# select2
|
# select2
|
||||||
|
|
||||||
SELECT2_JS = 'js/select2.min.js'
|
SELECT2_JS = "js/select2.min.js"
|
||||||
SELECT2_CSS = 'css/select2.min.css'
|
SELECT2_CSS = "css/select2.min.css"
|
||||||
SELECT2_I18N_PATH = ''
|
SELECT2_I18N_PATH = ""
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'auth.backend.HSWawOAuth2',
|
"auth.backend.HSWawOAuth2",
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
)
|
)
|
||||||
|
|
||||||
SOCIAL_AUTH_PIPELINE = (
|
SOCIAL_AUTH_PIPELINE = (
|
||||||
'social_core.pipeline.social_auth.social_details',
|
"social_core.pipeline.social_auth.social_details",
|
||||||
'social_core.pipeline.social_auth.social_uid',
|
"social_core.pipeline.social_auth.social_uid",
|
||||||
'social_core.pipeline.social_auth.social_user',
|
"social_core.pipeline.social_auth.social_user",
|
||||||
'social_core.pipeline.user.get_username',
|
"social_core.pipeline.user.get_username",
|
||||||
'social_core.pipeline.social_auth.associate_by_email',
|
"social_core.pipeline.social_auth.associate_by_email",
|
||||||
'auth.pipeline.associate_by_personal_email',
|
"auth.pipeline.associate_by_personal_email",
|
||||||
'social_core.pipeline.user.create_user',
|
"social_core.pipeline.user.create_user",
|
||||||
'social_core.pipeline.social_auth.associate_user',
|
"social_core.pipeline.social_auth.associate_user",
|
||||||
'social_core.pipeline.social_auth.load_extra_data',
|
"social_core.pipeline.social_auth.load_extra_data",
|
||||||
'social_core.pipeline.user.user_details',
|
"social_core.pipeline.user.user_details",
|
||||||
'auth.pipeline.staff_me_up',
|
"auth.pipeline.staff_me_up",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = "en-us"
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = "UTC"
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
@ -162,32 +162,32 @@ USE_TZ = True
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
os.path.join(BASE_DIR, "static"),
|
os.path.join(BASE_DIR, "static"),
|
||||||
]
|
]
|
||||||
|
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = env('MEDIA_ROOT', os.path.join(BASE_DIR, "media"))
|
MEDIA_ROOT = env("MEDIA_ROOT", os.path.join(BASE_DIR, "media"))
|
||||||
|
|
||||||
# REST Framework
|
# REST Framework
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
# Use Django's standard `django.contrib.auth` permissions,
|
# Use Django's standard `django.contrib.auth` permissions,
|
||||||
# or allow read-only access for unauthenticated users.
|
# or allow read-only access for unauthenticated users.
|
||||||
'DEFAULT_PERMISSION_CLASSES': [
|
"DEFAULT_PERMISSION_CLASSES": [
|
||||||
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
|
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
|
||||||
|
],
|
||||||
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||||
|
"rest_framework.authentication.BasicAuthentication",
|
||||||
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
|
"rest_framework.authentication.TokenAuthentication",
|
||||||
],
|
],
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SOCIAL_AUTH_HSWAW_KEY = env('CLIENT_ID')
|
SOCIAL_AUTH_HSWAW_KEY = env("CLIENT_ID")
|
||||||
SOCIAL_AUTH_HSWAW_SECRET = env('SECRET')
|
SOCIAL_AUTH_HSWAW_SECRET = env("SECRET")
|
||||||
SOCIAL_AUTH_REDIRECT_IS_HTTPS = PROD
|
SOCIAL_AUTH_REDIRECT_IS_HTTPS = PROD
|
||||||
|
|
||||||
SOCIAL_AUTH_POSTGRES_JSONFIELD = True
|
SOCIAL_AUTH_POSTGRES_JSONFIELD = True
|
||||||
|
|
||||||
LABEL_API = env('LABEL_API', 'http://label.waw.hackerspace.pl:4567')
|
LABEL_API = env("LABEL_API", "http://label.waw.hackerspace.pl:4567")
|
||||||
|
|
|
@ -16,19 +16,25 @@ from auth.views import auth_redirect
|
||||||
|
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'items', apiviews.ItemViewSet)
|
router.register(r"items", apiviews.ItemViewSet)
|
||||||
router.register(r'labels', apiviews.LabelViewSet)
|
router.register(r"labels", apiviews.LabelViewSet)
|
||||||
|
|
||||||
# Wire up our API using automatic URL routing.
|
# Wire up our API using automatic URL routing.
|
||||||
# Additionally, we include login URLs for the browsable API.
|
# Additionally, we include login URLs for the browsable API.
|
||||||
urlpatterns = ([
|
urlpatterns = (
|
||||||
url(r'^admin/login/.*', auth_redirect),
|
(
|
||||||
] if settings.PROD else []) + [
|
[
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r"^admin/login/.*", auth_redirect),
|
||||||
url(r'^select2/', include('django_select2.urls')),
|
]
|
||||||
|
if settings.PROD
|
||||||
url(r'^', include('storage.urls')),
|
else []
|
||||||
url(r'^api/1/', include(router.urls)),
|
)
|
||||||
] \
|
+ [
|
||||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \
|
url(r"^admin/", admin.site.urls),
|
||||||
|
url(r"^select2/", include("django_select2.urls")),
|
||||||
|
url(r"^", include("storage.urls")),
|
||||||
|
url(r"^api/1/", include(router.urls)),
|
||||||
|
]
|
||||||
|
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
)
|
||||||
|
|
|
@ -7,8 +7,8 @@ from django_select2.forms import ModelSelect2Widget, Select2MultipleWidget
|
||||||
from .models import Item, ItemImage, Category, Label
|
from .models import Item, ItemImage, Category, Label
|
||||||
from .widgets import ItemSelectWidget, PropsSelectWidget
|
from .widgets import ItemSelectWidget, PropsSelectWidget
|
||||||
|
|
||||||
class ModelAdminMixin(object):
|
|
||||||
|
|
||||||
|
class ModelAdminMixin(object):
|
||||||
def has_add_permission(self, request, obj=None):
|
def has_add_permission(self, request, obj=None):
|
||||||
return request.user.is_authenticated()
|
return request.user.is_authenticated()
|
||||||
|
|
||||||
|
@ -24,10 +24,10 @@ class ItemForm(forms.ModelForm):
|
||||||
model = Item
|
model = Item
|
||||||
exclude = []
|
exclude = []
|
||||||
widgets = {
|
widgets = {
|
||||||
'parent': ItemSelectWidget(model=Item),
|
"parent": ItemSelectWidget(model=Item),
|
||||||
'categories': Select2MultipleWidget,
|
"categories": Select2MultipleWidget,
|
||||||
'props': PropsSelectWidget
|
"props": PropsSelectWidget,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ItemImageInline(ModelAdminMixin, admin.TabularInline):
|
class ItemImageInline(ModelAdminMixin, admin.TabularInline):
|
||||||
|
@ -39,30 +39,29 @@ class LabelInline(ModelAdminMixin, admin.TabularInline):
|
||||||
model = Label
|
model = Label
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ItemAdmin(ModelAdminMixin, admin.ModelAdmin):
|
class ItemAdmin(ModelAdminMixin, admin.ModelAdmin):
|
||||||
list_display = ('_name',)
|
list_display = ("_name",)
|
||||||
list_filter = ('categories',)
|
list_filter = ("categories",)
|
||||||
form = ItemForm
|
form = ItemForm
|
||||||
inlines = [ItemImageInline, LabelInline]
|
inlines = [ItemImageInline, LabelInline]
|
||||||
save_on_top = True
|
save_on_top = True
|
||||||
|
|
||||||
def _name(self, obj):
|
def _name(self, obj):
|
||||||
return '-' * obj.get_level() + '> ' + obj.name
|
return "-" * obj.get_level() + "> " + obj.name
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
super(ItemAdmin, self).save_model(request, obj, form, change)
|
super(ItemAdmin, self).save_model(request, obj, form, change)
|
||||||
|
|
||||||
# Store last input parent to use as default on next creation
|
# Store last input parent to use as default on next creation
|
||||||
if obj.parent:
|
if obj.parent:
|
||||||
request.session['last-parent'] = str(obj.parent.uuid)
|
request.session["last-parent"] = str(obj.parent.uuid)
|
||||||
else:
|
else:
|
||||||
request.session['last-parent'] = str(obj.uuid)
|
request.session["last-parent"] = str(obj.uuid)
|
||||||
|
|
||||||
def get_changeform_initial_data(self, request):
|
def get_changeform_initial_data(self, request):
|
||||||
data = {
|
data = {
|
||||||
'parent': request.GET.get('parent') or request.session.get('last-parent')
|
"parent": request.GET.get("parent") or request.session.get("last-parent")
|
||||||
}
|
}
|
||||||
|
|
||||||
data.update(super(ItemAdmin, self).get_changeform_initial_data(request))
|
data.update(super(ItemAdmin, self).get_changeform_initial_data(request))
|
||||||
return data
|
return data
|
||||||
|
@ -70,11 +69,9 @@ class ItemAdmin(ModelAdminMixin, admin.ModelAdmin):
|
||||||
class Media:
|
class Media:
|
||||||
js = (
|
js = (
|
||||||
# Required by select2
|
# Required by select2
|
||||||
'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
|
"https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js",
|
||||||
)
|
)
|
||||||
css = {
|
css = {"all": ("css/admin.css",)}
|
||||||
'all': ('css/admin.css',)
|
|
||||||
}
|
|
||||||
|
|
||||||
def response_action(self, request, queryset):
|
def response_action(self, request, queryset):
|
||||||
with Item.disabled_tree_trigger():
|
with Item.disabled_tree_trigger():
|
||||||
|
@ -85,8 +82,8 @@ class NormalModelAdmin(ModelAdminMixin, admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin.site.site_title = 'Hackerspace Storage Admin'
|
admin.site.site_title = "Hackerspace Storage Admin"
|
||||||
admin.site.site_header = 'Hackerspace Storage Admin'
|
admin.site.site_header = "Hackerspace Storage Admin"
|
||||||
|
|
||||||
admin.site.register(Item, ItemAdmin)
|
admin.site.register(Item, ItemAdmin)
|
||||||
admin.site.register(Category, NormalModelAdmin)
|
admin.site.register(Category, NormalModelAdmin)
|
||||||
|
@ -94,13 +91,14 @@ admin.site.register(Category, NormalModelAdmin)
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
User.add_to_class('get_short_name', User.get_username)
|
User.add_to_class("get_short_name", User.get_username)
|
||||||
User.add_to_class('get_full_name', User.get_username)
|
User.add_to_class("get_full_name", User.get_username)
|
||||||
|
|
||||||
admin.site.unregister(User)
|
admin.site.unregister(User)
|
||||||
admin.site.unregister(Group)
|
admin.site.unregister(Group)
|
||||||
|
|
||||||
from social_django.admin import UserSocialAuth, Nonce, Association
|
from social_django.admin import UserSocialAuth, Nonce, Association
|
||||||
|
|
||||||
admin.site.unregister(UserSocialAuth)
|
admin.site.unregister(UserSocialAuth)
|
||||||
admin.site.unregister(Nonce)
|
admin.site.unregister(Nonce)
|
||||||
admin.site.unregister(Association)
|
admin.site.unregister(Association)
|
||||||
|
|
|
@ -17,7 +17,7 @@ class SmartSearchFilterBackend(filters.BaseFilterBackend):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
search_query = request.query_params.get('smartsearch', None)
|
search_query = request.query_params.get("smartsearch", None)
|
||||||
if search_query:
|
if search_query:
|
||||||
return apply_smart_search(search_query, queryset)
|
return apply_smart_search(search_query, queryset)
|
||||||
|
|
||||||
|
@ -28,12 +28,13 @@ class LabelViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint that allows items to be viewed or edited.
|
API endpoint that allows items to be viewed or edited.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = Label.objects
|
queryset = Label.objects
|
||||||
serializer_class = LabelSerializer
|
serializer_class = LabelSerializer
|
||||||
|
|
||||||
@detail_route(methods=['post'], permission_classes=[AllowAny])
|
@detail_route(methods=["post"], permission_classes=[AllowAny])
|
||||||
def print(self, request, pk):
|
def print(self, request, pk):
|
||||||
quantity = min(int(request.query_params.get('quantity', 1)), 5)
|
quantity = min(int(request.query_params.get("quantity", 1)), 5)
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
for _ in range(quantity):
|
for _ in range(quantity):
|
||||||
obj.print()
|
obj.print()
|
||||||
|
@ -44,11 +45,11 @@ class ItemViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint that allows items to be viewed or edited.
|
API endpoint that allows items to be viewed or edited.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = Item.objects
|
queryset = Item.objects
|
||||||
serializer_class = ItemSerializer
|
serializer_class = ItemSerializer
|
||||||
filter_backends = (SmartSearchFilterBackend, filters.OrderingFilter)
|
filter_backends = (SmartSearchFilterBackend, filters.OrderingFilter)
|
||||||
ordering_fields = '__all__'
|
ordering_fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Item.get_roots()
|
return Item.get_roots()
|
||||||
|
@ -63,7 +64,7 @@ class ItemViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
def get_item_by_id_or_label(self, id):
|
def get_item_by_id_or_label(self, id):
|
||||||
try:
|
try:
|
||||||
item = Item.objects.get(uuid__startswith=id) # look up by short id
|
item = Item.objects.get(uuid__startswith=id) # look up by short id
|
||||||
return item
|
return item
|
||||||
except Item.DoesNotExist:
|
except Item.DoesNotExist:
|
||||||
try:
|
try:
|
||||||
|
@ -72,10 +73,10 @@ class ItemViewSet(viewsets.ModelViewSet):
|
||||||
except Label.DoesNotExist:
|
except Label.DoesNotExist:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|
||||||
@detail_route(methods=['post'], permission_classes=[AllowAny])
|
@detail_route(methods=["post"], permission_classes=[AllowAny])
|
||||||
def print(self, request, pk):
|
def print(self, request, pk):
|
||||||
# todo: deduplicate
|
# todo: deduplicate
|
||||||
quantity = min(int(request.query_params.get('quantity', 1)), 5)
|
quantity = min(int(request.query_params.get("quantity", 1)), 5)
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
for _ in range(quantity):
|
for _ in range(quantity):
|
||||||
obj.print()
|
obj.print()
|
||||||
|
@ -84,19 +85,27 @@ class ItemViewSet(viewsets.ModelViewSet):
|
||||||
@detail_route()
|
@detail_route()
|
||||||
def children(self, request, pk):
|
def children(self, request, pk):
|
||||||
item = self.get_object()
|
item = self.get_object()
|
||||||
return Response(self.serializer_class(item.get_children().all(), many=True).data)
|
return Response(
|
||||||
|
self.serializer_class(item.get_children().all(), many=True).data
|
||||||
|
)
|
||||||
|
|
||||||
@detail_route()
|
@detail_route()
|
||||||
def ancestors(self, request, pk):
|
def ancestors(self, request, pk):
|
||||||
item = self.get_object()
|
item = self.get_object()
|
||||||
return Response(self.serializer_class(item.get_ancestors().all(), many=True).data)
|
return Response(
|
||||||
|
self.serializer_class(item.get_ancestors().all(), many=True).data
|
||||||
|
)
|
||||||
|
|
||||||
@detail_route()
|
@detail_route()
|
||||||
def descendants(self, request, pk):
|
def descendants(self, request, pk):
|
||||||
item = self.get_object()
|
item = self.get_object()
|
||||||
return Response(self.serializer_class(item.get_descendants().all(), many=True).data)
|
return Response(
|
||||||
|
self.serializer_class(item.get_descendants().all(), many=True).data
|
||||||
|
)
|
||||||
|
|
||||||
@detail_route()
|
@detail_route()
|
||||||
def siblings(self, request, pk):
|
def siblings(self, request, pk):
|
||||||
item = self.get_object()
|
item = self.get_object()
|
||||||
return Response(self.serializer_class(item.get_siblings().all(), many=True).data)
|
return Response(
|
||||||
|
self.serializer_class(item.get_siblings().all(), many=True).data
|
||||||
|
)
|
||||||
|
|
|
@ -4,4 +4,4 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class StorageConfig(AppConfig):
|
class StorageConfig(AppConfig):
|
||||||
name = 'storage'
|
name = "storage"
|
||||||
|
|
|
@ -3,28 +3,29 @@ from storage.models import Item
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Imports book library from specified wiki page dump'
|
help = "Imports book library from specified wiki page dump"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('parent')
|
parser.add_argument("parent")
|
||||||
parser.add_argument('file')
|
parser.add_argument("file")
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
with open(options['file']) as fd:
|
with open(options["file"]) as fd:
|
||||||
sio = StringIO(fd.read())
|
sio = StringIO(fd.read())
|
||||||
reader = csv.reader(sio, delimiter='|')
|
reader = csv.reader(sio, delimiter="|")
|
||||||
parent = Item.objects.get(pk=options['parent'])
|
parent = Item.objects.get(pk=options["parent"])
|
||||||
for line in reader:
|
for line in reader:
|
||||||
line = list(map(str.strip, line))
|
line = list(map(str.strip, line))
|
||||||
item = Item(parent=parent)
|
item = Item(parent=parent)
|
||||||
item.name = line[2]
|
item.name = line[2]
|
||||||
item.props['author'] = line[1]
|
item.props["author"] = line[1]
|
||||||
item.props['owner'] = line[3]
|
item.props["owner"] = line[3]
|
||||||
item.props['can_borrow'] = line[4]
|
item.props["can_borrow"] = line[4]
|
||||||
item.props['borrowed_by'] = line[5]
|
item.props["borrowed_by"] = line[5]
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
self.stdout.write(self.style.NOTICE('Book added: %r') % item)
|
self.stdout.write(self.style.NOTICE("Book added: %r") % item)
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS('Successfully imported data'))
|
self.stdout.write(self.style.SUCCESS("Successfully imported data"))
|
||||||
|
|
|
@ -7,20 +7,26 @@ import django_hstore.fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = []
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Item',
|
name="Item",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('name', models.TextField()),
|
"id",
|
||||||
('description', models.TextField()),
|
models.AutoField(
|
||||||
('props', django_hstore.fields.DictionaryField()),
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.TextField()),
|
||||||
|
("description", models.TextField()),
|
||||||
|
("props", django_hstore.fields.DictionaryField()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,8 +10,16 @@ import uuid
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
replaces = [
|
||||||
replaces = [('storage', '0001_initial'), ('storage', '0002_auto_20160929_2125'), ('storage', '0003_auto_20160929_2134'), ('storage', '0004_auto_20160929_2143'), ('storage', '0005_auto_20160929_2151'), ('storage', '0006_auto_20160929_2153'), ('storage', '0007_auto_20160929_2153'), ('storage', '0008_item_state')]
|
("storage", "0001_initial"),
|
||||||
|
("storage", "0002_auto_20160929_2125"),
|
||||||
|
("storage", "0003_auto_20160929_2134"),
|
||||||
|
("storage", "0004_auto_20160929_2143"),
|
||||||
|
("storage", "0005_auto_20160929_2151"),
|
||||||
|
("storage", "0006_auto_20160929_2153"),
|
||||||
|
("storage", "0007_auto_20160929_2153"),
|
||||||
|
("storage", "0008_item_state"),
|
||||||
|
]
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
|
@ -21,62 +29,114 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Item',
|
name="Item",
|
||||||
fields=[
|
fields=[
|
||||||
('name', models.TextField()),
|
("name", models.TextField()),
|
||||||
('description', models.TextField(blank=True)),
|
("description", models.TextField(blank=True)),
|
||||||
('props', django_hstore.fields.DictionaryField()),
|
("props", django_hstore.fields.DictionaryField()),
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
|
"uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ItemImage',
|
name="ItemImage",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('image', models.ImageField(upload_to='')),
|
"id",
|
||||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='storage.Item')),
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("image", models.ImageField(upload_to="")),
|
||||||
|
(
|
||||||
|
"item",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="images",
|
||||||
|
to="storage.Item",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Category',
|
name="Category",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('name', models.CharField(max_length=127)),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=127)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='categories',
|
name="categories",
|
||||||
field=models.ManyToManyField(to='storage.Category'),
|
field=models.ManyToManyField(to="storage.Category"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='owner',
|
name="owner",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_items', to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="owned_items",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='taken_by',
|
name="taken_by",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='taken_items', to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="taken_items",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='taken_on',
|
name="taken_on",
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='taken_until',
|
name="taken_until",
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='description',
|
name="description",
|
||||||
field=models.TextField(blank=True, null=True),
|
field=models.TextField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='state',
|
name="state",
|
||||||
field=models.CharField(choices=[('present', 'Present'), ('taken', 'Taken'), ('broken', 'Broken'), ('missing', 'Missing')], default='present', max_length=31),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("present", "Present"),
|
||||||
|
("taken", "Taken"),
|
||||||
|
("broken", "Broken"),
|
||||||
|
("missing", "Missing"),
|
||||||
|
],
|
||||||
|
default="present",
|
||||||
|
max_length=31,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,22 +9,26 @@ from tree.operations import CreateTreeTrigger
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('storage', '0001_squashed_0008_item_state'),
|
("storage", "0001_squashed_0008_item_state"),
|
||||||
('tree', '0001_initial'),
|
("tree", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='parent',
|
name="parent",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='storage.Item'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="storage.Item",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='path',
|
name="path",
|
||||||
field=tree.fields.PathField(),
|
field=tree.fields.PathField(),
|
||||||
),
|
),
|
||||||
CreateTreeTrigger('storage.Item'),
|
CreateTreeTrigger("storage.Item"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,36 +8,42 @@ import django_hstore.fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('storage', '0002_auto_20170215_0115'),
|
("storage", "0002_auto_20170215_0115"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Label',
|
name="Label",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.CharField(max_length=64, primary_key=True, serialize=False)),
|
(
|
||||||
('revision', models.IntegerField()),
|
"id",
|
||||||
|
models.CharField(max_length=64, primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
("revision", models.IntegerField()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='item',
|
name="item",
|
||||||
options={'ordering': ('path',)},
|
options={"ordering": ("path",)},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='categories',
|
name="categories",
|
||||||
field=models.ManyToManyField(blank=True, to='storage.Category'),
|
field=models.ManyToManyField(blank=True, to="storage.Category"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='item',
|
model_name="item",
|
||||||
name='props',
|
name="props",
|
||||||
field=django_hstore.fields.DictionaryField(blank=True),
|
field=django_hstore.fields.DictionaryField(blank=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='label',
|
model_name="label",
|
||||||
name='item',
|
name="item",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='labels', to='storage.Item'),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="labels",
|
||||||
|
to="storage.Item",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,25 +7,30 @@ import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('storage', '0003_auto_20170424_2002'),
|
("storage", "0003_auto_20170424_2002"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='label',
|
model_name="label",
|
||||||
name='revision',
|
name="revision",
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='label',
|
model_name="label",
|
||||||
name='created',
|
name="created",
|
||||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
field=models.DateTimeField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='label',
|
model_name="label",
|
||||||
name='style',
|
name="style",
|
||||||
field=models.CharField(choices=[('basic_99012_v1', 'Basic Dymo 89x36mm label')], default='basic_99012_v1', max_length=32),
|
field=models.CharField(
|
||||||
|
choices=[("basic_99012_v1", "Basic Dymo 89x36mm label")],
|
||||||
|
default="basic_99012_v1",
|
||||||
|
max_length=32,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,14 +4,14 @@ from __future__ import unicode_literals
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
from tree.operations import CreateTreeTrigger, DeleteTreeTrigger, RebuildPaths
|
from tree.operations import CreateTreeTrigger, DeleteTreeTrigger, RebuildPaths
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('storage', '0004_auto_20170528_1945'),
|
("storage", "0004_auto_20170528_1945"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
DeleteTreeTrigger('storage.Item'),
|
DeleteTreeTrigger("storage.Item"),
|
||||||
CreateTreeTrigger('storage.Item', order_by=('name',)),
|
CreateTreeTrigger("storage.Item", order_by=("name",)),
|
||||||
RebuildPaths('storage.Item')
|
RebuildPaths("storage.Item"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,15 +6,14 @@ from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('storage', '0005'),
|
("storage", "0005"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='category',
|
model_name="category",
|
||||||
name='icon_id',
|
name="icon_id",
|
||||||
field=models.CharField(blank=True, max_length=64, null=True),
|
field=models.CharField(blank=True, max_length=64, null=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,11 +14,11 @@ import requests
|
||||||
|
|
||||||
|
|
||||||
STATES = (
|
STATES = (
|
||||||
('present', 'Present'),
|
("present", "Present"),
|
||||||
('taken', 'Taken'),
|
("taken", "Taken"),
|
||||||
('broken', 'Broken'),
|
("broken", "Broken"),
|
||||||
('missing', 'Missing'),
|
("missing", "Missing"),
|
||||||
('depleted', 'Depleted'),
|
("depleted", "Depleted"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class Category(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ["name"]
|
||||||
verbose_name_plural = "categories"
|
verbose_name_plural = "categories"
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,10 +42,11 @@ class Category(models.Model):
|
||||||
# also qrcody w stylu //s/ID (żeby się resolvowało w sieci lokalnej)
|
# also qrcody w stylu //s/ID (żeby się resolvowało w sieci lokalnej)
|
||||||
# Also ID zawierające część name
|
# Also ID zawierające część name
|
||||||
|
|
||||||
|
|
||||||
class Item(models.Model, TreeModelMixin):
|
class Item(models.Model, TreeModelMixin):
|
||||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
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)
|
||||||
path = PathField()
|
path = PathField()
|
||||||
|
|
||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
|
@ -53,9 +54,11 @@ class Item(models.Model, TreeModelMixin):
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
state = models.CharField(max_length=31, choices=STATES, default=STATES[0][0])
|
state = models.CharField(max_length=31, choices=STATES, default=STATES[0][0])
|
||||||
categories = models.ManyToManyField(Category, blank=True)
|
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")
|
||||||
|
|
||||||
taken_by = models.ForeignKey(User, null=True, blank=True, related_name='taken_items')
|
taken_by = models.ForeignKey(
|
||||||
|
User, null=True, blank=True, related_name="taken_items"
|
||||||
|
)
|
||||||
taken_on = models.DateTimeField(blank=True, null=True)
|
taken_on = models.DateTimeField(blank=True, null=True)
|
||||||
taken_until = models.DateTimeField(blank=True, null=True)
|
taken_until = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
|
@ -65,19 +68,20 @@ class Item(models.Model, TreeModelMixin):
|
||||||
|
|
||||||
def short_id(self):
|
def short_id(self):
|
||||||
# let's just hope we never have 4 294 967 296 things :)
|
# let's just hope we never have 4 294 967 296 things :)
|
||||||
return str(self.pk)[:8] # collisions? what collisions?
|
return str(self.pk)[:8] # collisions? what collisions?
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '- ' * (self.get_level() or 0) + self.name
|
return "- " * (self.get_level() or 0) + self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
return reverse('item-display', kwargs={'pk': str(self.pk)})
|
|
||||||
|
return reverse("item-display", kwargs={"pk": str(self.pk)})
|
||||||
|
|
||||||
def get_or_create_label(self, **kwargs):
|
def get_or_create_label(self, **kwargs):
|
||||||
defaults = {
|
defaults = {
|
||||||
'id': re.sub('[^A-Z0-9]', '', self.name.upper())[:16],
|
"id": re.sub("[^A-Z0-9]", "", self.name.upper())[:16],
|
||||||
}
|
}
|
||||||
|
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
|
|
||||||
|
@ -92,33 +96,35 @@ class Item(models.Model, TreeModelMixin):
|
||||||
def print(self):
|
def print(self):
|
||||||
# todo: deduplicate
|
# todo: deduplicate
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
'{}/api/1/print/{}'.format(settings.LABEL_API, self.short_id()))
|
"{}/api/1/print/{}".format(settings.LABEL_API, self.short_id())
|
||||||
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('path',)
|
ordering = ("path",)
|
||||||
|
|
||||||
|
|
||||||
class ItemImage(models.Model):
|
class ItemImage(models.Model):
|
||||||
item = models.ForeignKey(Item, related_name='images')
|
item = models.ForeignKey(Item, related_name="images")
|
||||||
image = models.ImageField()
|
image = models.ImageField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}'.format(self.image.name)
|
return "{}".format(self.image.name)
|
||||||
|
|
||||||
|
|
||||||
class Label(models.Model):
|
class Label(models.Model):
|
||||||
id = models.CharField(max_length=64, primary_key=True)
|
id = models.CharField(max_length=64, primary_key=True)
|
||||||
item = models.ForeignKey(Item, related_name='labels')
|
item = models.ForeignKey(Item, related_name="labels")
|
||||||
style = models.CharField(max_length=32, choices=(
|
style = models.CharField(
|
||||||
('basic_99012_v1', 'Basic Dymo 89x36mm label'),
|
max_length=32,
|
||||||
), default='basic_99012_v1')
|
choices=(("basic_99012_v1", "Basic Dymo 89x36mm label"),),
|
||||||
|
default="basic_99012_v1",
|
||||||
|
)
|
||||||
created = models.DateTimeField(auto_now_add=True, blank=True)
|
created = models.DateTimeField(auto_now_add=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}'.format(self.id)
|
return "{}".format(self.id)
|
||||||
|
|
||||||
def print(self):
|
def print(self):
|
||||||
resp = requests.post(
|
resp = requests.post("{}/api/1/print/{}".format(settings.LABEL_API, self.id))
|
||||||
'{}/api/1/print/{}'.format(settings.LABEL_API, self.id))
|
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|
|
@ -5,16 +5,37 @@ from rest_framework_hstore.serializers import HStoreSerializer
|
||||||
|
|
||||||
|
|
||||||
class ItemSerializer(HStoreSerializer):
|
class ItemSerializer(HStoreSerializer):
|
||||||
categories = serializers.SlugRelatedField(queryset=Category.objects, many=True, slug_field='name')
|
categories = serializers.SlugRelatedField(
|
||||||
owner = serializers.SlugRelatedField(queryset=User.objects, slug_field='username')
|
queryset=Category.objects, many=True, slug_field="name"
|
||||||
taken_by = serializers.SlugRelatedField(queryset=User.objects, slug_field='username')
|
)
|
||||||
|
owner = serializers.SlugRelatedField(queryset=User.objects, slug_field="username")
|
||||||
|
taken_by = serializers.SlugRelatedField(
|
||||||
|
queryset=User.objects, slug_field="username"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Item
|
model = Item
|
||||||
fields = ('uuid', 'short_id', 'name', 'description', 'props', 'state', 'parent', 'labels', 'owner', 'taken_by', 'taken_on', 'taken_until', 'categories')
|
fields = (
|
||||||
|
"uuid",
|
||||||
|
"short_id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"props",
|
||||||
|
"state",
|
||||||
|
"parent",
|
||||||
|
"labels",
|
||||||
|
"owner",
|
||||||
|
"taken_by",
|
||||||
|
"taken_on",
|
||||||
|
"taken_until",
|
||||||
|
"categories",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LabelSerializer(serializers.ModelSerializer):
|
class LabelSerializer(serializers.ModelSerializer):
|
||||||
item = ItemSerializer(required=False)
|
item = ItemSerializer(required=False)
|
||||||
item_id = serializers.PrimaryKeyRelatedField(queryset=Item.objects, source='item')
|
item_id = serializers.PrimaryKeyRelatedField(queryset=Item.objects, source="item")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Label
|
model = Label
|
||||||
fields = ('id', 'item', 'item_id', 'style')
|
fields = ("id", "item", "item_id", "style")
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from storage.views import (
|
from storage.views import (
|
||||||
index, search, item_display, label_lookup, apitoken, ItemSelectView,
|
index,
|
||||||
PropSelectView
|
search,
|
||||||
|
item_display,
|
||||||
|
label_lookup,
|
||||||
|
apitoken,
|
||||||
|
ItemSelectView,
|
||||||
|
PropSelectView,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', index),
|
url(r"^$", index),
|
||||||
url(r'^search$', search),
|
url(r"^search$", search),
|
||||||
url(r'^apitoken$', apitoken),
|
url(r"^apitoken$", apitoken),
|
||||||
url(r'^item/(?P<pk>.*)$', item_display, name='item-display'),
|
url(r"^item/(?P<pk>.*)$", item_display, name="item-display"),
|
||||||
url(r'^autocomplete.json$', ItemSelectView.as_view(), name='item-complete'),
|
url(r"^autocomplete.json$", ItemSelectView.as_view(), name="item-complete"),
|
||||||
url(r'^autocomplete_prop.json$', PropSelectView.as_view(), name='prop-complete'),
|
url(r"^autocomplete_prop.json$", PropSelectView.as_view(), name="prop-complete"),
|
||||||
url(r'^(?P<pk>[^/]*)$', label_lookup, name='label-lookup'),
|
url(r"^(?P<pk>[^/]*)$", label_lookup, name="label-lookup"),
|
||||||
url('', include('social_django.urls', namespace='social')),
|
url("", include("social_django.urls", namespace="social")),
|
||||||
]
|
]
|
||||||
|
|
140
storage/views.py
140
storage/views.py
|
@ -13,29 +13,30 @@ from storage.models import Item, Label
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
|
||||||
def apply_smart_search(query, objects):
|
def apply_smart_search(query, objects):
|
||||||
general_term = []
|
general_term = []
|
||||||
|
|
||||||
filters = {}
|
filters = {}
|
||||||
|
|
||||||
for prop in shlex.split(query):
|
for prop in shlex.split(query):
|
||||||
if ':' not in prop:
|
if ":" not in prop:
|
||||||
general_term.append(prop)
|
general_term.append(prop)
|
||||||
else:
|
else:
|
||||||
key, value = prop.split(':', 1)
|
key, value = prop.split(":", 1)
|
||||||
if key in ['owner', 'taken_by']:
|
if key in ["owner", "taken_by"]:
|
||||||
filters[key + '__username'] = value
|
filters[key + "__username"] = value
|
||||||
elif hasattr(Item, key):
|
elif hasattr(Item, key):
|
||||||
filters[key + '__search'] = value
|
filters[key + "__search"] = value
|
||||||
elif key == 'ancestor':
|
elif key == "ancestor":
|
||||||
objects = Item.objects.get(pk=value).get_children()
|
objects = Item.objects.get(pk=value).get_children()
|
||||||
elif key == 'prop' or value:
|
elif key == "prop" or value:
|
||||||
if key == 'prop':
|
if key == "prop":
|
||||||
key, _, value = value.partition(':')
|
key, _, value = value.partition(":")
|
||||||
if not value:
|
if not value:
|
||||||
filters['props__isnull'] = {key: False}
|
filters["props__isnull"] = {key: False}
|
||||||
else:
|
else:
|
||||||
filters['props__contains'] = {key: value}
|
filters["props__contains"] = {key: value}
|
||||||
else:
|
else:
|
||||||
# "Whatever:"
|
# "Whatever:"
|
||||||
general_term.append(prop)
|
general_term.append(prop)
|
||||||
|
@ -44,35 +45,39 @@ def apply_smart_search(query, objects):
|
||||||
|
|
||||||
if not general_term:
|
if not general_term:
|
||||||
return objects
|
return objects
|
||||||
general_term = ' '.join(general_term)
|
general_term = " ".join(general_term)
|
||||||
|
|
||||||
objects = objects.annotate(
|
objects = (
|
||||||
search=SearchVector('name', 'description', 'props', config='simple'),
|
objects.annotate(
|
||||||
similarity=TrigramSimilarity('name', general_term)
|
search=SearchVector("name", "description", "props", config="simple"),
|
||||||
).filter(
|
similarity=TrigramSimilarity("name", general_term),
|
||||||
Q(similarity__gte=0.15) | Q(search__contains=general_term)
|
)
|
||||||
).order_by('-similarity')
|
.filter(Q(similarity__gte=0.15) | Q(search__contains=general_term))
|
||||||
|
.order_by("-similarity")
|
||||||
|
)
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
return render(request, 'results.html', {
|
return render(request, "results.html", {"results": Item.get_roots()})
|
||||||
'results': Item.get_roots()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def search(request):
|
def search(request):
|
||||||
query = request.GET.get('q', '')
|
query = request.GET.get("q", "")
|
||||||
|
|
||||||
results = apply_smart_search(query, Item.objects).all()
|
results = apply_smart_search(query, Item.objects).all()
|
||||||
|
|
||||||
if results and (len(results) == 1 or getattr(results[0], 'similarity', 0) == 1):
|
if results and (len(results) == 1 or getattr(results[0], "similarity", 0) == 1):
|
||||||
return redirect(results[0])
|
return redirect(results[0])
|
||||||
|
|
||||||
return render(request, 'results.html', {
|
return render(
|
||||||
'query': query,
|
request,
|
||||||
'results': results,
|
"results.html",
|
||||||
})
|
{
|
||||||
|
"query": query,
|
||||||
|
"results": results,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def item_display(request, pk):
|
def item_display(request, pk):
|
||||||
|
@ -83,18 +88,22 @@ def item_display(request, pk):
|
||||||
labels = item.labels.all()
|
labels = item.labels.all()
|
||||||
has_one_label = len(labels) == 1
|
has_one_label = len(labels) == 1
|
||||||
|
|
||||||
return render(request, 'item.html', {
|
return render(
|
||||||
'title': item.name,
|
request,
|
||||||
'item': item,
|
"item.html",
|
||||||
'categories': item.categories.all(),
|
{
|
||||||
'props': sorted(item.props.items()),
|
"title": item.name,
|
||||||
'images': item.images.all(),
|
"item": item,
|
||||||
'labels': labels,
|
"categories": item.categories.all(),
|
||||||
'has_one_label': has_one_label,
|
"props": sorted(item.props.items()),
|
||||||
'history': LogEntry.objects.filter(object_id=item.pk),
|
"images": item.images.all(),
|
||||||
'ancestors': item.get_ancestors(),
|
"labels": labels,
|
||||||
'children': item.get_children().prefetch_related('categories'),
|
"has_one_label": has_one_label,
|
||||||
})
|
"history": LogEntry.objects.filter(object_id=item.pk),
|
||||||
|
"ancestors": item.get_ancestors(),
|
||||||
|
"children": item.get_children().prefetch_related("categories"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def label_lookup(request, pk):
|
def label_lookup(request, pk):
|
||||||
|
@ -114,49 +123,56 @@ def label_lookup(request, pk):
|
||||||
def apitoken(request):
|
def apitoken(request):
|
||||||
print(Token)
|
print(Token)
|
||||||
token, created = Token.objects.get_or_create(user=request.user)
|
token, created = Token.objects.get_or_create(user=request.user)
|
||||||
return HttpResponse(token.key, content_type='text/plain')
|
return HttpResponse(token.key, content_type="text/plain")
|
||||||
|
|
||||||
|
|
||||||
class ItemSelectView(AutoResponseView):
|
class ItemSelectView(AutoResponseView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.widget = self.get_widget_or_404()
|
self.widget = self.get_widget_or_404()
|
||||||
self.term = kwargs.get('term', request.GET.get('term', ''))
|
self.term = kwargs.get("term", request.GET.get("term", ""))
|
||||||
self.object_list = apply_smart_search(self.term, Item.objects)
|
self.object_list = apply_smart_search(self.term, Item.objects)
|
||||||
context = self.get_context_data()
|
context = self.get_context_data()
|
||||||
return JsonResponse({
|
return JsonResponse(
|
||||||
'results': [
|
{
|
||||||
{
|
"results": [
|
||||||
'text': obj.name,
|
{
|
||||||
'path': [o.name for o in obj.get_ancestors()],
|
"text": obj.name,
|
||||||
'id': obj.pk,
|
"path": [o.name for o in obj.get_ancestors()],
|
||||||
}
|
"id": obj.pk,
|
||||||
for obj in context['object_list']
|
}
|
||||||
|
for obj in context["object_list"]
|
||||||
],
|
],
|
||||||
'more': context['page_obj'].has_next()
|
"more": context["page_obj"].has_next(),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PropSelectView(AutoResponseView):
|
class PropSelectView(AutoResponseView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# self.widget = self.get_widget_or_404()
|
# self.widget = self.get_widget_or_404()
|
||||||
self.term = kwargs.get('term', request.GET.get('term', ''))
|
self.term = kwargs.get("term", request.GET.get("term", ""))
|
||||||
# context = self.get_context_data()
|
# context = self.get_context_data()
|
||||||
with connection.cursor() as c:
|
with connection.cursor() as c:
|
||||||
c.execute("""
|
c.execute(
|
||||||
|
"""
|
||||||
SELECT key, count(*) FROM
|
SELECT key, count(*) FROM
|
||||||
(SELECT (each(props)).key FROM storage_item) AS stat
|
(SELECT (each(props)).key FROM storage_item) AS stat
|
||||||
WHERE key like %s
|
WHERE key like %s
|
||||||
GROUP BY key
|
GROUP BY key
|
||||||
ORDER BY count DESC, key
|
ORDER BY count DESC, key
|
||||||
limit 10;
|
limit 10;
|
||||||
""", ['%' + self.term + '%'])
|
""",
|
||||||
|
["%" + self.term + "%"],
|
||||||
|
)
|
||||||
props = [e[0] for e in c.fetchall()]
|
props = [e[0] for e in c.fetchall()]
|
||||||
return JsonResponse({
|
return JsonResponse(
|
||||||
'results': [
|
{
|
||||||
{
|
"results": [
|
||||||
'text': p,
|
{
|
||||||
'id': p,
|
"text": p,
|
||||||
}
|
"id": p,
|
||||||
for p in props
|
}
|
||||||
|
for p in props
|
||||||
],
|
],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -13,9 +13,8 @@ from django.contrib.admin.widgets import AdminTextareaWidget
|
||||||
|
|
||||||
|
|
||||||
class ItemSelectWidget(ModelSelect2Widget):
|
class ItemSelectWidget(ModelSelect2Widget):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['data_view'] = 'item-complete'
|
kwargs["data_view"] = "item-complete"
|
||||||
super(ItemSelectWidget, self).__init__(*args, **kwargs)
|
super(ItemSelectWidget, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
|
@ -30,21 +29,24 @@ class PropsSelectWidget(DictionaryFieldWidget):
|
||||||
if attrs is None:
|
if attrs is None:
|
||||||
attrs = {}
|
attrs = {}
|
||||||
# it's called "original" because it will be replaced by a copy
|
# it's called "original" because it will be replaced by a copy
|
||||||
attrs['class'] = 'hstore-original-textarea'
|
attrs["class"] = "hstore-original-textarea"
|
||||||
w = HeavySelect2Widget(data_view='prop-complete', attrs={'data-tags': 'true', 'class': 'hs-key'})
|
w = HeavySelect2Widget(
|
||||||
|
data_view="prop-complete", attrs={"data-tags": "true", "class": "hs-key"}
|
||||||
|
)
|
||||||
|
|
||||||
# get default HTML from AdminTextareaWidget
|
# get default HTML from AdminTextareaWidget
|
||||||
html = AdminTextareaWidget.render(self, name, value, attrs)
|
html = AdminTextareaWidget.render(self, name, value, attrs)
|
||||||
# prepare template context
|
# prepare template context
|
||||||
template_context = {
|
template_context = {
|
||||||
'field_name': name,
|
"field_name": name,
|
||||||
'STATIC_URL': settings.STATIC_URL,
|
"STATIC_URL": settings.STATIC_URL,
|
||||||
'use_svg': parse_version(get_version()) >= parse_version('1.9'), # use svg icons if django >= 1.9
|
"use_svg": parse_version(get_version())
|
||||||
'ajax_url': reverse('prop-complete'),
|
>= parse_version("1.9"), # use svg icons if django >= 1.9
|
||||||
'w': w.build_attrs(base_attrs=w.attrs)
|
"ajax_url": reverse("prop-complete"),
|
||||||
|
"w": w.build_attrs(base_attrs=w.attrs),
|
||||||
}
|
}
|
||||||
# get template object
|
# get template object
|
||||||
template = get_template('hstore_%s_widget.html' % self.admin_style)
|
template = get_template("hstore_%s_widget.html" % self.admin_style)
|
||||||
# render additional html
|
# render additional html
|
||||||
additional_html = template.render(template_context)
|
additional_html = template.render(template_context)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue