Blackify the code (autoformat)

pull/1/head
Dariusz Niemczyk 2023-07-11 15:34:35 +02:00
parent 3fdf788168
commit 659f04ce9c
No known key found for this signature in database
GPG Key ID: 28DFE7164F497CB6
21 changed files with 519 additions and 369 deletions

View File

@ -1,36 +1,35 @@
from social_core.backends.oauth import BaseOAuth2
from six.moves.urllib_parse import urlencode, unquote
class HSWawOAuth2(BaseOAuth2):
"""Hackerspace OAuth authentication backend"""
name = 'hswaw'
ID_KEY = 'username'
AUTHORIZATION_URL = 'https://sso.hackerspace.pl/oauth/authorize'
ACCESS_TOKEN_URL = 'https://sso.hackerspace.pl/oauth/token'
DEFAULT_SCOPE = ['profile:read']
name = "hswaw"
ID_KEY = "username"
AUTHORIZATION_URL = "https://sso.hackerspace.pl/oauth/authorize"
ACCESS_TOKEN_URL = "https://sso.hackerspace.pl/oauth/token"
DEFAULT_SCOPE = ["profile:read"]
REDIRECT_STATE = False
SCOPE_SEPARATOR = ','
EXTRA_DATA = [
('expires', 'expires_in')
]
SCOPE_SEPARATOR = ","
EXTRA_DATA = [("expires", "expires_in")]
def get_user_details(self, response):
"""Return user details from Hackerspace account"""
personal_email = None
if response.get('personal_email'):
personal_email = response.get('personal_email')[0]
if response.get("personal_email"):
personal_email = response.get("personal_email")[0]
return {'username': response.get('username'),
'email': response.get('email'),
'personal_email': personal_email,
}
return {
"username": response.get("username"),
"email": response.get("email"),
"personal_email": personal_email,
}
def user_data(self, access_token, *args, **kwargs):
"""Loads user data from service"""
url = 'https://sso.hackerspace.pl/api/1/profile'
headers = {
'Authorization': 'Bearer {}'.format(access_token)
}
url = "https://sso.hackerspace.pl/api/1/profile"
headers = {"Authorization": "Bearer {}".format(access_token)}
return self.get_json(url, headers=headers)
def auth_url(self):
@ -40,4 +39,4 @@ class HSWawOAuth2(BaseOAuth2):
params.update(self.get_scope_argument())
params.update(self.auth_extra_arguments())
params = urlencode(params)
return '{0}?{1}'.format(self.authorization_url(), params)
return "{0}?{1}".format(self.authorization_url(), params)

View File

@ -5,13 +5,19 @@ from django.contrib.auth.models import Group
def staff_me_up(backend, details, response, uid, user, *args, **kwargs):
user.is_staff = True
try:
user.groups.set([Group.objects.get(name='member')])
user.groups.set([Group.objects.get(name="member")])
except Group.DoesNotExist:
pass
user.save()
def associate_by_personal_email(backend, details, user=None, *args, **kwargs):
return associate_by_email(backend, {
'email': details.get('personal_email'),
}, user, *args, **kwargs)
return associate_by_email(
backend,
{
"email": details.get("personal_email"),
},
user,
*args,
**kwargs
)

View File

@ -1,4 +1,5 @@
from django.shortcuts import redirect
def auth_redirect(request):
return redirect('social:begin', 'hswaw')
return redirect("social:begin", "hswaw")

View File

@ -12,97 +12,97 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
import os
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, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_ROOT = os.path.join(BASE_DIR, 'build_static')
PROD = os.getenv('SPEJSTORE_ENV') == 'prod'
STATIC_ROOT = os.path.join(BASE_DIR, "build_static")
PROD = os.getenv("SPEJSTORE_ENV") == "prod"
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
# 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!
DEBUG = not PROD
ALLOWED_HOSTS = env('ALLOWED_HOSTS', 'devinventory,inventory.waw.hackerspace.pl,i,inventory').split(',')
LOGIN_REDIRECT_URL = '/admin/'
ALLOWED_HOSTS = env(
"ALLOWED_HOSTS", "devinventory,inventory.waw.hackerspace.pl,i,inventory"
).split(",")
LOGIN_REDIRECT_URL = "/admin/"
# Application definition
INSTALLED_APPS = [
'flat_responsive',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.postgres',
'social_django',
'django_hstore',
'tree',
'django_select2',
'rest_framework',
'rest_framework.authtoken',
'django_markdown2',
'storage',
"flat_responsive",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.postgres",
"social_django",
"django_hstore",
"tree",
"django_select2",
"rest_framework",
"rest_framework.authtoken",
"django_markdown2",
"storage",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'social_django.middleware.SocialAuthExceptionMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"social_django.middleware.SocialAuthExceptionMiddleware",
]
ROOT_URLCONF = 'spejstore.urls'
ROOT_URLCONF = "spejstore.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates/'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["templates/"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"social_django.context_processors.backends",
"social_django.context_processors.login_redirect",
],
},
},
]
WSGI_APPLICATION = 'spejstore.wsgi.application'
WSGI_APPLICATION = "spejstore.wsgi.application"
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': env('DB_ENGINE', 'django.db.backends.postgresql_psycopg2'),
'NAME': env('DB_NAME', 'postgres'),
'USER': env('DB_USER', 'postgres'),
'PASSWORD': env('DB_PASSWORD', None),
'HOST': env('DB_HOST', 'db'),
'PORT': env('DB_PORT', 5432),
"default": {
"ENGINE": env("DB_ENGINE", "django.db.backends.postgresql_psycopg2"),
"NAME": env("DB_NAME", "postgres"),
"USER": env("DB_USER", "postgres"),
"PASSWORD": env("DB_PASSWORD", None),
"HOST": env("DB_HOST", "db"),
"PORT": env("DB_PORT", 5432),
}
}
@ -112,49 +112,49 @@ DATABASES = {
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_JS = 'js/select2.min.js'
SELECT2_CSS = 'css/select2.min.css'
SELECT2_I18N_PATH = ''
SELECT2_JS = "js/select2.min.js"
SELECT2_CSS = "css/select2.min.css"
SELECT2_I18N_PATH = ""
AUTHENTICATION_BACKENDS = (
'auth.backend.HSWawOAuth2',
'django.contrib.auth.backends.ModelBackend',
"auth.backend.HSWawOAuth2",
"django.contrib.auth.backends.ModelBackend",
)
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.social_auth.associate_by_email',
'auth.pipeline.associate_by_personal_email',
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
'auth.pipeline.staff_me_up',
"social_core.pipeline.social_auth.social_details",
"social_core.pipeline.social_auth.social_uid",
"social_core.pipeline.social_auth.social_user",
"social_core.pipeline.user.get_username",
"social_core.pipeline.social_auth.associate_by_email",
"auth.pipeline.associate_by_personal_email",
"social_core.pipeline.user.create_user",
"social_core.pipeline.social_auth.associate_user",
"social_core.pipeline.social_auth.load_extra_data",
"social_core.pipeline.user.user_details",
"auth.pipeline.staff_me_up",
)
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
@ -162,32 +162,32 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
STATIC_URL = '/static/'
STATIC_URL = "/static/"
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
]
MEDIA_URL = '/media/'
MEDIA_ROOT = env('MEDIA_ROOT', os.path.join(BASE_DIR, "media"))
MEDIA_URL = "/media/"
MEDIA_ROOT = env("MEDIA_ROOT", os.path.join(BASE_DIR, "media"))
# REST Framework
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
"DEFAULT_PERMISSION_CLASSES": [
"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_SECRET = env('SECRET')
SOCIAL_AUTH_HSWAW_KEY = env("CLIENT_ID")
SOCIAL_AUTH_HSWAW_SECRET = env("SECRET")
SOCIAL_AUTH_REDIRECT_IS_HTTPS = PROD
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")

View File

@ -16,19 +16,25 @@ from auth.views import auth_redirect
router = routers.DefaultRouter()
router.register(r'items', apiviews.ItemViewSet)
router.register(r'labels', apiviews.LabelViewSet)
router.register(r"items", apiviews.ItemViewSet)
router.register(r"labels", apiviews.LabelViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = ([
url(r'^admin/login/.*', auth_redirect),
] if settings.PROD else []) + [
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) \
urlpatterns = (
(
[
url(r"^admin/login/.*", auth_redirect),
]
if settings.PROD
else []
)
+ [
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)
)

View File

@ -7,8 +7,8 @@ from django_select2.forms import ModelSelect2Widget, Select2MultipleWidget
from .models import Item, ItemImage, Category, Label
from .widgets import ItemSelectWidget, PropsSelectWidget
class ModelAdminMixin(object):
class ModelAdminMixin(object):
def has_add_permission(self, request, obj=None):
return request.user.is_authenticated()
@ -24,10 +24,10 @@ class ItemForm(forms.ModelForm):
model = Item
exclude = []
widgets = {
'parent': ItemSelectWidget(model=Item),
'categories': Select2MultipleWidget,
'props': PropsSelectWidget
}
"parent": ItemSelectWidget(model=Item),
"categories": Select2MultipleWidget,
"props": PropsSelectWidget,
}
class ItemImageInline(ModelAdminMixin, admin.TabularInline):
@ -39,30 +39,29 @@ class LabelInline(ModelAdminMixin, admin.TabularInline):
model = Label
class ItemAdmin(ModelAdminMixin, admin.ModelAdmin):
list_display = ('_name',)
list_filter = ('categories',)
list_display = ("_name",)
list_filter = ("categories",)
form = ItemForm
inlines = [ItemImageInline, LabelInline]
save_on_top = True
def _name(self, obj):
return '-' * obj.get_level() + '> ' + obj.name
return "-" * obj.get_level() + "> " + obj.name
def save_model(self, request, obj, form, change):
super(ItemAdmin, self).save_model(request, obj, form, change)
# Store last input parent to use as default on next creation
if obj.parent:
request.session['last-parent'] = str(obj.parent.uuid)
request.session["last-parent"] = str(obj.parent.uuid)
else:
request.session['last-parent'] = str(obj.uuid)
request.session["last-parent"] = str(obj.uuid)
def get_changeform_initial_data(self, request):
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))
return data
@ -70,11 +69,9 @@ class ItemAdmin(ModelAdminMixin, admin.ModelAdmin):
class Media:
js = (
# Required by select2
'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
)
css = {
'all': ('css/admin.css',)
}
"https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js",
)
css = {"all": ("css/admin.css",)}
def response_action(self, request, queryset):
with Item.disabled_tree_trigger():
@ -85,8 +82,8 @@ class NormalModelAdmin(ModelAdminMixin, admin.ModelAdmin):
pass
admin.site.site_title = 'Hackerspace Storage Admin'
admin.site.site_header = 'Hackerspace Storage Admin'
admin.site.site_title = "Hackerspace Storage Admin"
admin.site.site_header = "Hackerspace Storage Admin"
admin.site.register(Item, ItemAdmin)
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 Group
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_short_name", User.get_username)
User.add_to_class("get_full_name", User.get_username)
admin.site.unregister(User)
admin.site.unregister(Group)
from social_django.admin import UserSocialAuth, Nonce, Association
admin.site.unregister(UserSocialAuth)
admin.site.unregister(Nonce)
admin.site.unregister(Association)

View File

@ -17,7 +17,7 @@ class SmartSearchFilterBackend(filters.BaseFilterBackend):
"""
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:
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.
"""
queryset = Label.objects
serializer_class = LabelSerializer
@detail_route(methods=['post'], permission_classes=[AllowAny])
@detail_route(methods=["post"], permission_classes=[AllowAny])
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()
for _ in range(quantity):
obj.print()
@ -44,11 +45,11 @@ class ItemViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows items to be viewed or edited.
"""
queryset = Item.objects
serializer_class = ItemSerializer
filter_backends = (SmartSearchFilterBackend, filters.OrderingFilter)
ordering_fields = '__all__'
ordering_fields = "__all__"
def get_queryset(self):
return Item.get_roots()
@ -63,7 +64,7 @@ class ItemViewSet(viewsets.ModelViewSet):
def get_item_by_id_or_label(self, id):
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
except Item.DoesNotExist:
try:
@ -72,10 +73,10 @@ class ItemViewSet(viewsets.ModelViewSet):
except Label.DoesNotExist:
raise Http404()
@detail_route(methods=['post'], permission_classes=[AllowAny])
@detail_route(methods=["post"], permission_classes=[AllowAny])
def print(self, request, pk):
# 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()
for _ in range(quantity):
obj.print()
@ -84,19 +85,27 @@ class ItemViewSet(viewsets.ModelViewSet):
@detail_route()
def children(self, request, pk):
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()
def ancestors(self, request, pk):
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()
def descendants(self, request, pk):
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()
def siblings(self, request, pk):
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
)

View File

@ -4,4 +4,4 @@ from django.apps import AppConfig
class StorageConfig(AppConfig):
name = 'storage'
name = "storage"

View File

@ -3,28 +3,29 @@ from storage.models import Item
from io import StringIO
import csv
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):
parser.add_argument('parent')
parser.add_argument('file')
parser.add_argument("parent")
parser.add_argument("file")
def handle(self, *args, **options):
with open(options['file']) as fd:
with open(options["file"]) as fd:
sio = StringIO(fd.read())
reader = csv.reader(sio, delimiter='|')
parent = Item.objects.get(pk=options['parent'])
reader = csv.reader(sio, delimiter="|")
parent = Item.objects.get(pk=options["parent"])
for line in reader:
line = list(map(str.strip, line))
item = Item(parent=parent)
item.name = line[2]
item.props['author'] = line[1]
item.props['owner'] = line[3]
item.props['can_borrow'] = line[4]
item.props['borrowed_by'] = line[5]
item.props["author"] = line[1]
item.props["owner"] = line[3]
item.props["can_borrow"] = line[4]
item.props["borrowed_by"] = line[5]
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"))

View File

@ -7,20 +7,26 @@ import django_hstore.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Item',
name="Item",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField()),
('description', models.TextField()),
('props', django_hstore.fields.DictionaryField()),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.TextField()),
("description", models.TextField()),
("props", django_hstore.fields.DictionaryField()),
],
),
]

View File

@ -10,8 +10,16 @@ import uuid
class Migration(migrations.Migration):
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')]
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"),
]
initial = True
@ -21,62 +29,114 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Item',
name="Item",
fields=[
('name', models.TextField()),
('description', models.TextField(blank=True)),
('props', django_hstore.fields.DictionaryField()),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
("name", models.TextField()),
("description", models.TextField(blank=True)),
("props", django_hstore.fields.DictionaryField()),
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
],
),
migrations.CreateModel(
name='ItemImage',
name="ItemImage",
fields=[
('id', 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')),
(
"id",
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(
name='Category',
name="Category",
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(
model_name='item',
name='categories',
field=models.ManyToManyField(to='storage.Category'),
model_name="item",
name="categories",
field=models.ManyToManyField(to="storage.Category"),
),
migrations.AddField(
model_name='item',
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),
model_name="item",
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,
),
),
migrations.AddField(
model_name='item',
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),
model_name="item",
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,
),
),
migrations.AddField(
model_name='item',
name='taken_on',
model_name="item",
name="taken_on",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='item',
name='taken_until',
model_name="item",
name="taken_until",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='item',
name='description',
model_name="item",
name="description",
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='item',
name='state',
field=models.CharField(choices=[('present', 'Present'), ('taken', 'Taken'), ('broken', 'Broken'), ('missing', 'Missing')], default='present', max_length=31),
model_name="item",
name="state",
field=models.CharField(
choices=[
("present", "Present"),
("taken", "Taken"),
("broken", "Broken"),
("missing", "Missing"),
],
default="present",
max_length=31,
),
),
]

View File

@ -9,22 +9,26 @@ from tree.operations import CreateTreeTrigger
class Migration(migrations.Migration):
dependencies = [
('storage', '0001_squashed_0008_item_state'),
('tree', '0001_initial'),
("storage", "0001_squashed_0008_item_state"),
("tree", "0001_initial"),
]
operations = [
migrations.AddField(
model_name='item',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='storage.Item'),
model_name="item",
name="parent",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="storage.Item",
),
),
migrations.AddField(
model_name='item',
name='path',
model_name="item",
name="path",
field=tree.fields.PathField(),
),
CreateTreeTrigger('storage.Item'),
CreateTreeTrigger("storage.Item"),
]

View File

@ -8,36 +8,42 @@ import django_hstore.fields
class Migration(migrations.Migration):
dependencies = [
('storage', '0002_auto_20170215_0115'),
("storage", "0002_auto_20170215_0115"),
]
operations = [
migrations.CreateModel(
name='Label',
name="Label",
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(
name='item',
options={'ordering': ('path',)},
name="item",
options={"ordering": ("path",)},
),
migrations.AlterField(
model_name='item',
name='categories',
field=models.ManyToManyField(blank=True, to='storage.Category'),
model_name="item",
name="categories",
field=models.ManyToManyField(blank=True, to="storage.Category"),
),
migrations.AlterField(
model_name='item',
name='props',
model_name="item",
name="props",
field=django_hstore.fields.DictionaryField(blank=True),
),
migrations.AddField(
model_name='label',
name='item',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='labels', to='storage.Item'),
model_name="label",
name="item",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="labels",
to="storage.Item",
),
),
]

View File

@ -7,25 +7,30 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('storage', '0003_auto_20170424_2002'),
("storage", "0003_auto_20170424_2002"),
]
operations = [
migrations.RemoveField(
model_name='label',
name='revision',
model_name="label",
name="revision",
),
migrations.AddField(
model_name='label',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
model_name="label",
name="created",
field=models.DateTimeField(
auto_now_add=True, default=django.utils.timezone.now
),
preserve_default=False,
),
migrations.AddField(
model_name='label',
name='style',
field=models.CharField(choices=[('basic_99012_v1', 'Basic Dymo 89x36mm label')], default='basic_99012_v1', max_length=32),
model_name="label",
name="style",
field=models.CharField(
choices=[("basic_99012_v1", "Basic Dymo 89x36mm label")],
default="basic_99012_v1",
max_length=32,
),
),
]

View File

@ -4,14 +4,14 @@ from __future__ import unicode_literals
from django.db import migrations, models
from tree.operations import CreateTreeTrigger, DeleteTreeTrigger, RebuildPaths
class Migration(migrations.Migration):
class Migration(migrations.Migration):
dependencies = [
('storage', '0004_auto_20170528_1945'),
("storage", "0004_auto_20170528_1945"),
]
operations = [
DeleteTreeTrigger('storage.Item'),
CreateTreeTrigger('storage.Item', order_by=('name',)),
RebuildPaths('storage.Item')
DeleteTreeTrigger("storage.Item"),
CreateTreeTrigger("storage.Item", order_by=("name",)),
RebuildPaths("storage.Item"),
]

View File

@ -6,15 +6,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('storage', '0005'),
("storage", "0005"),
]
operations = [
migrations.AddField(
model_name='category',
name='icon_id',
model_name="category",
name="icon_id",
field=models.CharField(blank=True, max_length=64, null=True),
),
]

View File

@ -14,11 +14,11 @@ import requests
STATES = (
('present', 'Present'),
('taken', 'Taken'),
('broken', 'Broken'),
('missing', 'Missing'),
('depleted', 'Depleted'),
("present", "Present"),
("taken", "Taken"),
("broken", "Broken"),
("missing", "Missing"),
("depleted", "Depleted"),
)
@ -31,7 +31,7 @@ class Category(models.Model):
return self.name
class Meta:
ordering = ['name']
ordering = ["name"]
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 ID zawierające część name
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)
path = PathField()
name = models.TextField()
@ -53,9 +54,11 @@ 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")
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_until = models.DateTimeField(blank=True, null=True)
@ -65,19 +68,20 @@ class Item(models.Model, TreeModelMixin):
def short_id(self):
# 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):
return '- ' * (self.get_level() or 0) + self.name
return "- " * (self.get_level() or 0) + self.name
def get_absolute_url(self):
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):
defaults = {
'id': re.sub('[^A-Z0-9]', '', self.name.upper())[:16],
}
"id": re.sub("[^A-Z0-9]", "", self.name.upper())[:16],
}
defaults.update(kwargs)
@ -92,33 +96,35 @@ class Item(models.Model, TreeModelMixin):
def print(self):
# todo: deduplicate
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()
class Meta:
ordering = ('path',)
ordering = ("path",)
class ItemImage(models.Model):
item = models.ForeignKey(Item, related_name='images')
item = models.ForeignKey(Item, related_name="images")
image = models.ImageField()
def __str__(self):
return '{}'.format(self.image.name)
return "{}".format(self.image.name)
class Label(models.Model):
id = models.CharField(max_length=64, primary_key=True)
item = models.ForeignKey(Item, related_name='labels')
style = models.CharField(max_length=32, choices=(
('basic_99012_v1', 'Basic Dymo 89x36mm label'),
), default='basic_99012_v1')
item = models.ForeignKey(Item, related_name="labels")
style = models.CharField(
max_length=32,
choices=(("basic_99012_v1", "Basic Dymo 89x36mm label"),),
default="basic_99012_v1",
)
created = models.DateTimeField(auto_now_add=True, blank=True)
def __str__(self):
return '{}'.format(self.id)
return "{}".format(self.id)
def print(self):
resp = requests.post(
'{}/api/1/print/{}'.format(settings.LABEL_API, self.id))
resp = requests.post("{}/api/1/print/{}".format(settings.LABEL_API, self.id))
resp.raise_for_status()

View File

@ -5,16 +5,37 @@ from rest_framework_hstore.serializers import HStoreSerializer
class ItemSerializer(HStoreSerializer):
categories = serializers.SlugRelatedField(queryset=Category.objects, many=True, slug_field='name')
owner = serializers.SlugRelatedField(queryset=User.objects, slug_field='username')
taken_by = serializers.SlugRelatedField(queryset=User.objects, slug_field='username')
categories = serializers.SlugRelatedField(
queryset=Category.objects, many=True, slug_field="name"
)
owner = serializers.SlugRelatedField(queryset=User.objects, slug_field="username")
taken_by = serializers.SlugRelatedField(
queryset=User.objects, slug_field="username"
)
class Meta:
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):
item = ItemSerializer(required=False)
item_id = serializers.PrimaryKeyRelatedField(queryset=Item.objects, source='item')
item_id = serializers.PrimaryKeyRelatedField(queryset=Item.objects, source="item")
class Meta:
model = Label
fields = ('id', 'item', 'item_id', 'style')
fields = ("id", "item", "item_id", "style")

View File

@ -1,16 +1,21 @@
from django.conf.urls import include, url
from storage.views import (
index, search, item_display, label_lookup, apitoken, ItemSelectView,
PropSelectView
index,
search,
item_display,
label_lookup,
apitoken,
ItemSelectView,
PropSelectView,
)
urlpatterns = [
url(r'^$', index),
url(r'^search$', search),
url(r'^apitoken$', apitoken),
url(r'^item/(?P<pk>.*)$', item_display, name='item-display'),
url(r'^autocomplete.json$', ItemSelectView.as_view(), name='item-complete'),
url(r'^autocomplete_prop.json$', PropSelectView.as_view(), name='prop-complete'),
url(r'^(?P<pk>[^/]*)$', label_lookup, name='label-lookup'),
url('', include('social_django.urls', namespace='social')),
url(r"^$", index),
url(r"^search$", search),
url(r"^apitoken$", apitoken),
url(r"^item/(?P<pk>.*)$", item_display, name="item-display"),
url(r"^autocomplete.json$", ItemSelectView.as_view(), name="item-complete"),
url(r"^autocomplete_prop.json$", PropSelectView.as_view(), name="prop-complete"),
url(r"^(?P<pk>[^/]*)$", label_lookup, name="label-lookup"),
url("", include("social_django.urls", namespace="social")),
]

View File

@ -13,29 +13,30 @@ from storage.models import Item, Label
from django.contrib.auth.decorators import login_required
from rest_framework.authtoken.models import Token
def apply_smart_search(query, objects):
general_term = []
filters = {}
for prop in shlex.split(query):
if ':' not in prop:
if ":" not in prop:
general_term.append(prop)
else:
key, value = prop.split(':', 1)
if key in ['owner', 'taken_by']:
filters[key + '__username'] = value
key, value = prop.split(":", 1)
if key in ["owner", "taken_by"]:
filters[key + "__username"] = value
elif hasattr(Item, key):
filters[key + '__search'] = value
elif key == 'ancestor':
filters[key + "__search"] = value
elif key == "ancestor":
objects = Item.objects.get(pk=value).get_children()
elif key == 'prop' or value:
if key == 'prop':
key, _, value = value.partition(':')
elif key == "prop" or value:
if key == "prop":
key, _, value = value.partition(":")
if not value:
filters['props__isnull'] = {key: False}
filters["props__isnull"] = {key: False}
else:
filters['props__contains'] = {key: value}
filters["props__contains"] = {key: value}
else:
# "Whatever:"
general_term.append(prop)
@ -44,35 +45,39 @@ def apply_smart_search(query, objects):
if not general_term:
return objects
general_term = ' '.join(general_term)
general_term = " ".join(general_term)
objects = objects.annotate(
search=SearchVector('name', 'description', 'props', config='simple'),
similarity=TrigramSimilarity('name', general_term)
).filter(
Q(similarity__gte=0.15) | Q(search__contains=general_term)
).order_by('-similarity')
objects = (
objects.annotate(
search=SearchVector("name", "description", "props", config="simple"),
similarity=TrigramSimilarity("name", general_term),
)
.filter(Q(similarity__gte=0.15) | Q(search__contains=general_term))
.order_by("-similarity")
)
return objects
def index(request):
return render(request, 'results.html', {
'results': Item.get_roots()
})
return render(request, "results.html", {"results": Item.get_roots()})
def search(request):
query = request.GET.get('q', '')
query = request.GET.get("q", "")
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 render(request, 'results.html', {
'query': query,
'results': results,
})
return render(
request,
"results.html",
{
"query": query,
"results": results,
},
)
def item_display(request, pk):
@ -83,18 +88,22 @@ def item_display(request, pk):
labels = item.labels.all()
has_one_label = len(labels) == 1
return render(request, 'item.html', {
'title': item.name,
'item': item,
'categories': item.categories.all(),
'props': sorted(item.props.items()),
'images': item.images.all(),
'labels': labels,
'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'),
})
return render(
request,
"item.html",
{
"title": item.name,
"item": item,
"categories": item.categories.all(),
"props": sorted(item.props.items()),
"images": item.images.all(),
"labels": labels,
"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):
@ -114,49 +123,56 @@ def label_lookup(request, pk):
def apitoken(request):
print(Token)
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):
def get(self, request, *args, **kwargs):
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)
context = self.get_context_data()
return JsonResponse({
'results': [
{
'text': obj.name,
'path': [o.name for o in obj.get_ancestors()],
'id': obj.pk,
}
for obj in context['object_list']
return JsonResponse(
{
"results": [
{
"text": obj.name,
"path": [o.name for o in obj.get_ancestors()],
"id": obj.pk,
}
for obj in context["object_list"]
],
'more': context['page_obj'].has_next()
})
"more": context["page_obj"].has_next(),
}
)
class PropSelectView(AutoResponseView):
def get(self, request, *args, **kwargs):
# 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()
with connection.cursor() as c:
c.execute("""
c.execute(
"""
SELECT key, count(*) FROM
(SELECT (each(props)).key FROM storage_item) AS stat
WHERE key like %s
GROUP BY key
ORDER BY count DESC, key
limit 10;
""", ['%' + self.term + '%'])
""",
["%" + self.term + "%"],
)
props = [e[0] for e in c.fetchall()]
return JsonResponse({
'results': [
{
'text': p,
'id': p,
}
for p in props
return JsonResponse(
{
"results": [
{
"text": p,
"id": p,
}
for p in props
],
})
}
)

View File

@ -13,9 +13,8 @@ from django.contrib.admin.widgets import AdminTextareaWidget
class ItemSelectWidget(ModelSelect2Widget):
def __init__(self, *args, **kwargs):
kwargs['data_view'] = 'item-complete'
kwargs["data_view"] = "item-complete"
super(ItemSelectWidget, self).__init__(*args, **kwargs)
def label_from_instance(self, obj):
@ -30,21 +29,24 @@ class PropsSelectWidget(DictionaryFieldWidget):
if attrs is None:
attrs = {}
# it's called "original" because it will be replaced by a copy
attrs['class'] = 'hstore-original-textarea'
w = HeavySelect2Widget(data_view='prop-complete', attrs={'data-tags': 'true', 'class': 'hs-key'})
attrs["class"] = "hstore-original-textarea"
w = HeavySelect2Widget(
data_view="prop-complete", attrs={"data-tags": "true", "class": "hs-key"}
)
# get default HTML from AdminTextareaWidget
html = AdminTextareaWidget.render(self, name, value, attrs)
# prepare template context
template_context = {
'field_name': name,
'STATIC_URL': settings.STATIC_URL,
'use_svg': parse_version(get_version()) >= parse_version('1.9'), # use svg icons if django >= 1.9
'ajax_url': reverse('prop-complete'),
'w': w.build_attrs(base_attrs=w.attrs)
"field_name": name,
"STATIC_URL": settings.STATIC_URL,
"use_svg": parse_version(get_version())
>= parse_version("1.9"), # use svg icons if django >= 1.9
"ajax_url": reverse("prop-complete"),
"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_%s_widget.html" % self.admin_style)
# render additional html
additional_html = template.render(template_context)