Initial web UI, smart search, book import command

This commit is contained in:
Piotr Dobrowolski 2017-02-16 02:01:36 +01:00
parent 9ac7a56135
commit b653ec8e25
13 changed files with 7407 additions and 16 deletions

View File

@ -37,6 +37,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.postgres',
'django_hstore', 'django_hstore',
'tree', 'tree',
@ -59,7 +60,7 @@ ROOT_URLCONF = 'spejstore.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [], 'DIRS': ['templates/'],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@ -126,3 +127,7 @@ USE_TZ = True
# 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 = [
os.path.join(BASE_DIR, "static"),
]

View File

@ -2,20 +2,15 @@
The `urlpatterns` list routes URLs to views. For more information please see: The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.10/topics/http/urls/ https://docs.djangoproject.com/en/1.10/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
""" """
from django.conf.urls import url from django.conf.urls import url, include
from django.contrib import admin from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
url(r'^admin/', admin.site.urls), url(r'^admin/', admin.site.urls),
]
url(r'^', include('storage.urls')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

7174
static/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

View File

View File

View File

@ -0,0 +1,30 @@
from django.core.management.base import BaseCommand, CommandError
from storage.models import Item
from io import StringIO
import csv
class Command(BaseCommand):
help = 'Imports book library from specified wiki page dump'
def add_arguments(self, parser):
parser.add_argument('parent')
parser.add_argument('file')
def handle(self, *args, **options):
with open(options['file']) as fd:
sio = StringIO(fd.read())
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.save()
self.stdout.write(self.style.NOTICE('Book added: %r') % item)
self.stdout.write(self.style.SUCCESS('Successfully imported data'))

View File

@ -46,7 +46,11 @@ class Item(models.Model, TreeModelMixin):
objects = hstore.HStoreManager() objects = hstore.HStoreManager()
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):
from django.urls import reverse
return reverse('item-display', kwargs={'pk': str(self.pk)})
class Meta: class Meta:
ordering = ('path',) ordering = ('path',)

View File

@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form action="/search">
<div class="input-group input-group-lg">
<input type="text" class="form-control" name="q" placeholder="search term">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit"><i class="glyphicon glyphicon-search"></i></button>
</span>
</div>
<div class="text-right">
<input type="checkbox" id="smartsearch" name="smartsearch">
<label for="smartsearch">SmartSearch</label>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block content %}
<ol class="breadcrumb">
<li><a href="/item/">Everything</a></li>
{% for ancestor in ancestors %}
<li><a href="{{ ancestor.get_absolute_url }}">{{ ancestor.name }}</a></li>
{% endfor %}
<li class="active">{{ item.name }}</li>
</ol>
<h2>{{ item.name }} <small>{{ item.pk }}</small></h2>
<div class="row">
<div class="col-md-4">
{% if item.props %}
<table class="table table-hover table-striped">
<thead>
<tr>
<th>key</th>
<th>value</th>
</tr>
</thead>
{% for k, v in item.props.items %}
<tr><td>{{ k }}</td><td>{{ v }}</td></tr>
{% endfor %}
</table>
{% endif %}
</div>
</div>
{% if children %}
<h3>Children</h3>
<table class="table table-striped table-hover">
{% for child in children %}
<tr><td><a href="{{ child.get_absolute_url }}">{{ child.name }}</a></td></tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<table class="table table-striped table-hover">
{% for item in results %}
<tr>
<td>
{% for parent in item.get_ancestors %}
{{ parent.name }} &raquo;
{% endfor %}
<a href="{{ item.get_absolute_url }}">{{ item.name }}</a>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

8
storage/urls.py Normal file
View File

@ -0,0 +1,8 @@
from django.conf.urls import include, url
from storage.views import index, search, item_display
urlpatterns = [
url(r'^$', index),
url(r'^search$', search),
url(r'^item/(?P<pk>.*)$', item_display, name='item-display'),
]

View File

@ -1,3 +1,64 @@
from django.shortcuts import render from django.shortcuts import render, get_object_or_404
from storage.models import Item
from django.contrib.postgres.search import SearchVector
import shlex
# Create your views here. def apply_smart_search(query, objects):
general_term = []
filters = {}
for prop in shlex.split(query):
if ':' not in prop:
general_term.append(prop)
else:
key, value = prop.split(':', 1)
if hasattr(Item, key):
filters[key + '__search'] = value
elif key == 'prop' or value:
if key == 'prop':
key, value = value.split(':', 1)
if 'props__contains' not in filters:
filters['props__contains'] = {}
filters['props__contains'] = {key: value}
else:
# "Whatever:"
general_term.append(prop)
if general_term:
objects = objects.annotate(
search=SearchVector('name', 'description', 'props'),
)
filters['search'] = ' '.join(general_term)
objects = objects.filter(**filters)
return objects
def index(request):
return render(request, 'index.html')
def search(request):
query = request.GET.get('q', '')
results = apply_smart_search(query, Item.objects)
return render(request, 'results.html', {
'query': query,
'results': results.all(),
})
def item_display(request, pk):
if not pk:
return render(request, 'results.html', {
'results': Item.get_roots()
})
item = get_object_or_404(Item, pk=pk)
return render(request, 'item.html', {
'item': item,
'ancestors': item.get_ancestors(),
'children': item.get_children(),
})

40
templates/base.html Normal file
View File

@ -0,0 +1,40 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hackerspace Storage System</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}" media="screen">
<style>
.btn {
font-size: 1.4em;
text-transform: none;
margin-bottom: 10px;
padding: 15px;
}
.btn .glyphicon {
padding: 0 10px;
}
.btn-alt {
background: #4b176d;
color:white;
border-color: #2d0d42; /*#301934;*/
}
.btn-alt:hover, .btn-alt:active, .btn-alt:link, .btn-alt:visited {
color: white;
}
</style>
</head>
<body>
{% block body %}
<div class="container">
<h1 class="page-header">Warsaw Hackerspace <small class="hidden-sm hidden-xs">Enjoy your stay</small></h1>
{% block content %}
{% endblock %}
</div>
{% endblock %}
</body>
</html>