mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-09 19:40:19 +00:00
Add ajax-select app and improve some templates
This commit is contained in:
39
core/lookups.py
Normal file
39
core/lookups.py
Normal file
@ -0,0 +1,39 @@
|
||||
from ajax_select import register, LookupChannel
|
||||
|
||||
from core.views.site import search_user
|
||||
from core.models import User
|
||||
from counter.models import Product, Counter
|
||||
|
||||
@register('users')
|
||||
class UsersLookup(LookupChannel):
|
||||
model = User
|
||||
|
||||
def get_query(self, q, request):
|
||||
return search_user(q)
|
||||
|
||||
def format_match(self, obj):
|
||||
return obj.get_mini_item()
|
||||
|
||||
def format_item_display(self, item):
|
||||
return item.get_display_name()
|
||||
|
||||
@register('counters')
|
||||
class CountersLookup(LookupChannel):
|
||||
model = Counter
|
||||
|
||||
def get_query(self, q, request):
|
||||
return self.model.objects.filter(name__icontains=q)[:50]
|
||||
|
||||
def format_item_display(self, item):
|
||||
return item.name
|
||||
|
||||
@register('products')
|
||||
class ProductsLookup(LookupChannel):
|
||||
model = Product
|
||||
|
||||
def get_query(self, q, request):
|
||||
print(request.__dict__)
|
||||
return (self.model.objects.filter(name__icontains=q) | self.model.objects.filter(code__icontains=q))[:50]
|
||||
|
||||
def format_item_display(self, item):
|
||||
return item.name
|
@ -351,6 +351,20 @@ class User(AbstractBaseUser):
|
||||
def can_be_edited_by(self, user):
|
||||
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root
|
||||
|
||||
def get_mini_item(self):
|
||||
return """
|
||||
<div class="mini_profile_link" >
|
||||
<span>
|
||||
<img src="%s" alt="%s" />
|
||||
</span>
|
||||
<em>%s</em>
|
||||
</a>
|
||||
""" % (
|
||||
self.profile_pict.get_download_url() if self.profile_pict else "/static/core/img/na.gif",
|
||||
_("Profile"),
|
||||
self.get_display_name(),
|
||||
)
|
||||
|
||||
|
||||
class AnonymousUser(AuthAnonymousUser):
|
||||
def __init__(self, request):
|
||||
|
@ -1,192 +0,0 @@
|
||||
/* ----------------------------------------------------------------------------------------------------
|
||||
Super Form Reset
|
||||
----------------------------------------------------------------------------------------------------*/
|
||||
|
||||
form {
|
||||
margin: 0px auto;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
input,
|
||||
label,
|
||||
select,
|
||||
button,
|
||||
textarea
|
||||
{
|
||||
margin: 1px;
|
||||
border: none;
|
||||
padding: 1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
white-space: normal;
|
||||
background: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Browsers have different default form fonts */
|
||||
font-size: 13px;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
label {
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
/* Remove the stupid outer glow in Webkit */
|
||||
input:focus
|
||||
{
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* Box Sizing Reset
|
||||
-----------------------------------------------*/
|
||||
|
||||
/* All of our custom controls should be what we expect them to be */
|
||||
input,
|
||||
textarea
|
||||
{
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/* These elements are usually rendered a certain way by the browser */
|
||||
button,
|
||||
input[type=reset],
|
||||
input[type=button],
|
||||
input[type=submit],
|
||||
input[type=checkbox],
|
||||
input[type=radio],
|
||||
select
|
||||
{
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Text Inputs
|
||||
-----------------------------------------------*/
|
||||
|
||||
input[type=date],
|
||||
input[type=datetime],
|
||||
input[type=datetime-local],
|
||||
input[type=email],
|
||||
input[type=month],
|
||||
input[type=number],
|
||||
input[type=password],
|
||||
input[type=range],
|
||||
input[type=search],
|
||||
input[type=tel],
|
||||
input[type=text],
|
||||
input[type=time],
|
||||
input[type=url],
|
||||
input[type=week],
|
||||
textarea
|
||||
{
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Button Controls
|
||||
-----------------------------------------------*/
|
||||
|
||||
input[type=checkbox],
|
||||
input[type=radio]
|
||||
{
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
/* File Uploads
|
||||
-----------------------------------------------*/
|
||||
|
||||
input[type=file]
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/* Search Input
|
||||
-----------------------------------------------*/
|
||||
|
||||
/* Make webkit render the search input like a normal text field */
|
||||
input[type=search]
|
||||
{
|
||||
-webkit-appearance: textfield;
|
||||
-webkit-box-sizing: content-box;
|
||||
}
|
||||
|
||||
/* Turn off the recent search for webkit. It adds about 15px padding on the left */
|
||||
::-webkit-search-decoration
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Buttons
|
||||
-----------------------------------------------*/
|
||||
|
||||
button,
|
||||
input[type="reset"],
|
||||
input[type="button"],
|
||||
input[type="submit"]
|
||||
{
|
||||
/* Fix IE7 display bug */
|
||||
overflow: visible;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
padding: 3px;
|
||||
border: 1px solid black;
|
||||
border-radius: 2px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
input[type="reset"]:hover,
|
||||
input[type="button"]:hover,
|
||||
input[type="submit"]:hover
|
||||
{
|
||||
box-shadow: 0 0 2px #000;
|
||||
}
|
||||
|
||||
button:active,
|
||||
input[type="reset"]:active,
|
||||
input[type="button"]:active,
|
||||
input[type="submit"]:active
|
||||
{
|
||||
box-shadow: inset 0 0 2px #000;
|
||||
}
|
||||
|
||||
/* IE8 and FF freak out if this rule is within another selector */
|
||||
::-webkit-file-upload-button
|
||||
{
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* Textarea
|
||||
-----------------------------------------------*/
|
||||
|
||||
textarea
|
||||
{
|
||||
/* Move the label to the top */
|
||||
vertical-align: top;
|
||||
|
||||
/* Turn off scroll bars in IE unless needed */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Selects
|
||||
-----------------------------------------------*/
|
||||
|
||||
select
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
select[multiple]
|
||||
{
|
||||
/* Move the label to the top */
|
||||
vertical-align: top;
|
||||
}
|
||||
|
@ -160,22 +160,31 @@ tbody>tr:hover {
|
||||
float: right;
|
||||
padding: 10px;
|
||||
}
|
||||
#user_info_container {
|
||||
}
|
||||
#user_info {
|
||||
float: right;
|
||||
padding: 5px;
|
||||
width: 40%;
|
||||
margin: 0px auto;
|
||||
background: lightgrey;
|
||||
}
|
||||
|
||||
/*-----------------------------USER PROFILE----------------------------*/
|
||||
#user_profile {
|
||||
#user_profile_container {
|
||||
width: 80%;
|
||||
margin: 0px auto;
|
||||
}
|
||||
#user_profile {
|
||||
width: 100%;
|
||||
margin: 0px auto;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
#user_profile h4 { border-bottom: 1px solid grey; max-width: 60%; }
|
||||
#user_profile #pictures {
|
||||
width: 30%;
|
||||
max-width: 250px;
|
||||
max-height: 300px;
|
||||
float: right;
|
||||
font-style: italic;
|
||||
}
|
||||
@ -238,7 +247,14 @@ footer{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*--------------------------------MODALE-------------------------------*/
|
||||
/*---------------------------------FORMS-------------------------------*/
|
||||
form {
|
||||
margin: 0px auto;
|
||||
width: 60%;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
.choose_file_widget {
|
||||
display: none;
|
||||
}
|
||||
@ -247,24 +263,10 @@ footer{
|
||||
position: absolute;
|
||||
width: 97%;
|
||||
}
|
||||
.form-wrapper {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #DDD;
|
||||
opacity: 0.8;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
#user_edit * {
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
.form-wrapper>form {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
left: 30%;
|
||||
top: 40%;
|
||||
background-color: white;
|
||||
#user_edit img {
|
||||
width: 100px;
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
{% block head %}
|
||||
<title>{% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ static('core/base.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/form.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/multiple-select.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/js/ui/jquery-ui.min.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('ajax_select/css/ajax_select.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/style.css') }}">
|
||||
{% endblock %}
|
||||
</head>
|
||||
@ -70,6 +70,7 @@
|
||||
<script src="{{ static('core/js/jquery-3.1.0.min.js') }}"></script>
|
||||
<script src="{{ static('core/js/ui/jquery-ui.min.js') }}"></script>
|
||||
<script src="{{ static('core/js/multiple-select.js') }}"></script>
|
||||
<script src="{{ static('ajax_select/js/ajax_select.js') }}"></script>
|
||||
<script src="{{ static('core/js/script.js') }}"></script>
|
||||
<script>
|
||||
$('.select_single').multipleSelect({
|
||||
|
@ -14,3 +14,24 @@
|
||||
<em>{{ user.get_display_name() }}</em>
|
||||
</a>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro user_mini_profile(user) %}
|
||||
<div id="user_profile">
|
||||
<div id="pictures">
|
||||
{% if user.profile_pict %}
|
||||
<img src="{{ user.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}" />
|
||||
{% endif %}
|
||||
</div>
|
||||
<p>{{ user.get_full_name() }}</p>
|
||||
{% if user.nick_name %}
|
||||
<p id="nickname">« {{ user.nick_name }} »</p>
|
||||
{% endif %}
|
||||
{% if user.date_of_birth %}
|
||||
<p>{% trans %}Born: {% endtrans %}{{ user.date_of_birth|date("d/m/Y") }}</p>
|
||||
{% endif %}
|
||||
{% if user.promo %}
|
||||
<p><img src="{{ static('core/img/promo_%02d.png' % user.promo) }}" alt="Promo {{ user.promo }}" class="promo_pict" />
|
||||
{% trans %}Promo: {% endtrans %}{{ user.promo }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
@ -6,31 +6,33 @@
|
||||
|
||||
{% block infos %}
|
||||
|
||||
<div id="user_profile">
|
||||
<div id="pictures">
|
||||
{% if profile.profile_pict %}
|
||||
<img src="{{ profile.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}" />
|
||||
<div id="user_profile_container">
|
||||
<div id="user_profile">
|
||||
<div id="pictures">
|
||||
{% if profile.profile_pict %}
|
||||
<img src="{{ profile.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}" />
|
||||
{% endif %}
|
||||
<p><em>{{ profile.quote }}</em></p>
|
||||
</div>
|
||||
<h4>{{ profile.get_full_name() }}</h4>
|
||||
{% if profile.nick_name %}
|
||||
<p id="nickname">« {{ profile.nick_name }} »</p>
|
||||
{% endif %}
|
||||
{% if profile.date_of_birth %}
|
||||
<p>{% trans %}Born: {% endtrans %}{{ profile.date_of_birth|date("d/m/Y") }}</p>
|
||||
{% endif %}
|
||||
{% if profile.department != "NA" %}
|
||||
<p>{{ profile.department }}{{ profile.semester }}
|
||||
{% endif %}
|
||||
{% if profile.dpt_option %}
|
||||
<br>{% trans %}Option: {% endtrans %}{{ profile.dpt_option }}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if profile.promo %}
|
||||
<p><img src="{{ static('core/img/promo_%02d.png' % profile.promo) }}" alt="Promo {{ profile.promo }}" class="promo_pict" />
|
||||
{% trans %}Promo: {% endtrans %}{{ profile.promo }}</p>
|
||||
{% endif %}
|
||||
<p><em>{{ profile.quote }}</em></p>
|
||||
</div>
|
||||
<h4>{{ profile.get_full_name() }}</h4>
|
||||
{% if profile.nick_name %}
|
||||
<p id="nickname">« {{ profile.nick_name }} »</p>
|
||||
{% endif %}
|
||||
{% if profile.date_of_birth %}
|
||||
<p>{% trans %}Born: {% endtrans %}{{ profile.date_of_birth|date("d/m/Y") }}</p>
|
||||
{% endif %}
|
||||
{% if profile.department != "NA" %}
|
||||
<p>{{ profile.department }}{{ profile.semester }}
|
||||
{% endif %}
|
||||
{% if profile.dpt_option %}
|
||||
<br>{% trans %}Option: {% endtrans %}{{ profile.dpt_option }}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if profile.promo %}
|
||||
<p><img src="{{ static('core/img/promo_%02d.png' % profile.promo) }}" alt="Promo {{ profile.promo }}" class="promo_pict" />
|
||||
{% trans %}Promo: {% endtrans %}{{ profile.promo }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if user.membership.filter(end_date=None).exists() or user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) %}
|
||||
|
@ -6,19 +6,19 @@
|
||||
|
||||
{% block infos %}
|
||||
<h2>{% trans %}Edit user profile{% endtrans %}</h2>
|
||||
<form action="" method="post" enctype="multipart/form-data">
|
||||
<form action="" method="post" enctype="multipart/form-data" id="user_edit">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
<p>{{ field.errors }}<label for="{{ field.name }}">{{ field.label }}
|
||||
{%- if field.name == "profile_pict" and form.instance.profile_pict -%}
|
||||
<br>{% trans %}Current profile: {% endtrans %}
|
||||
<img src="{{ form.instance.profile_pict.get_download_url() }}" title="{% trans %}Profile{% endtrans %}" width="50px" /><br>
|
||||
<img src="{{ form.instance.profile_pict.get_download_url() }}" title="{% trans %}Profile{% endtrans %}" /><br>
|
||||
{%- elif field.name == "avatar_pict" and form.instance.avatar_pict -%}
|
||||
<br>{% trans %}Current avatar: {% endtrans %}
|
||||
<img src="{{ form.instance.avatar_pict.get_download_url() }}" title="{% trans %}Avatar{% endtrans %}" width="50px" /><br>
|
||||
<img src="{{ form.instance.avatar_pict.get_download_url() }}" title="{% trans %}Avatar{% endtrans %}" /><br>
|
||||
{%- elif field.name == "scrub_pict" and form.instance.scrub_pict -%}
|
||||
<br>{% trans %}Current scrub: {% endtrans %}
|
||||
<img src="{{ form.instance.scrub_pict.get_download_url() }}" title="{% trans %}Scrub{% endtrans %}" width="50px" /><br>
|
||||
<img src="{{ form.instance.scrub_pict.get_download_url() }}" title="{% trans %}Scrub{% endtrans %}" /><br>
|
||||
{%- endif %}</label> {{ field }}</p>
|
||||
{% endfor %}
|
||||
<p><input type="submit" value="{% trans %}Update{% endtrans %}" /></p>
|
||||
|
@ -1,19 +1,3 @@
|
||||
<div id="user_profile">
|
||||
<div id="pictures">
|
||||
{% if profile.profile_pict %}
|
||||
<img src="{{ profile.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}" />
|
||||
{% endif %}
|
||||
</div>
|
||||
<p>{{ profile.get_full_name() }}</p>
|
||||
{% if profile.nick_name %}
|
||||
<p id="nickname">« {{ profile.nick_name }} »</p>
|
||||
{% endif %}
|
||||
{% if profile.date_of_birth %}
|
||||
<p>{% trans %}Born: {% endtrans %}{{ profile.date_of_birth|date("d/m/Y") }}</p>
|
||||
{% endif %}
|
||||
{% if profile.promo %}
|
||||
<p><img src="{{ static('core/img/promo_%02d.png' % profile.promo) }}" alt="Promo {{ profile.promo }}" class="promo_pict" />
|
||||
{% trans %}Promo: {% endtrans %}{{ profile.promo }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% from "core/macros.jinja" import user_mini_profile %}
|
||||
|
||||
{{ user_mini_profile(profile) }}
|
||||
|
@ -6,6 +6,7 @@ urlpatterns = [
|
||||
url(r'^$', index, name='index'),
|
||||
url(r'^search/$', search_view, name='search'),
|
||||
url(r'^search_json/$', search_json, name='search_json'),
|
||||
url(r'^search_user/$', search_user_json, name='search_user'),
|
||||
|
||||
# Login and co
|
||||
url(r'^login/$', login, name='login'),
|
||||
|
@ -52,6 +52,20 @@ class SelectFile(TextInput):
|
||||
output += '<span name="' + name + '" class="choose_file_button">' + _("Choose file") + '</span>'
|
||||
return output
|
||||
|
||||
class SelectUser(TextInput):
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs:
|
||||
attrs['class'] = "select_user"
|
||||
else:
|
||||
attrs = {'class': "select_user"}
|
||||
output = '%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>' % {
|
||||
'content': super(SelectUser, self).render(name, value, attrs),
|
||||
'title': _("Choose user"),
|
||||
'name': name,
|
||||
}
|
||||
output += '<span name="' + name + '" class="choose_user_button">' + _("Choose user") + '</span>'
|
||||
return output
|
||||
|
||||
# Forms
|
||||
|
||||
class RegisteringForm(UserCreationForm):
|
||||
|
@ -15,36 +15,57 @@ from club.models import Club
|
||||
def index(request, context=None):
|
||||
return render(request, "core/index.jinja")
|
||||
|
||||
def search(query, as_json=False):
|
||||
result = {'users': None, 'clubs': None}
|
||||
def search_user(query, as_json=False):
|
||||
users = []
|
||||
if query:
|
||||
exact_nick = User.objects.filter(nick_name__iexact=query).all()
|
||||
nicks = User.objects.filter(nick_name__icontains=query).exclude(id__in=exact_nick).all()
|
||||
users = User.objects.filter(Q(first_name__icontains=query) |
|
||||
Q(last_name__icontains=query)).exclude(id__in=exact_nick).exclude(id__in=nicks).all()
|
||||
clubs = Club.objects.filter(name__icontains=query).all()
|
||||
nicks = nicks[:5]
|
||||
users = users[:5]
|
||||
clubs = clubs[:5]
|
||||
if as_json: # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
|
||||
exact_nick = json.loads(serializers.serialize('json', exact_nick, fields=('nick_name', 'last_name', 'first_name', 'profile_pict')))
|
||||
nicks = json.loads(serializers.serialize('json', nicks, fields=('nick_name', 'last_name', 'first_name', 'profile_pict')))
|
||||
users = json.loads(serializers.serialize('json', users, fields=('nick_name', 'last_name', 'first_name', 'profile_pict')))
|
||||
clubs = json.loads(serializers.serialize('json', clubs, fields=('name')))
|
||||
else:
|
||||
exact_nick = list(exact_nick)
|
||||
nicks = list(nicks)
|
||||
users = list(users)
|
||||
users = exact_nick + nicks + users
|
||||
return users
|
||||
|
||||
def search_club(query, as_json=False):
|
||||
clubs = []
|
||||
if query:
|
||||
clubs = Club.objects.filter(name__icontains=query).all()
|
||||
clubs = clubs[:5]
|
||||
if as_json: # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
|
||||
clubs = json.loads(serializers.serialize('json', clubs, fields=('name')))
|
||||
else:
|
||||
clubs = list(clubs)
|
||||
result['users'] = exact_nick + nicks + users
|
||||
result['clubs'] = clubs
|
||||
return result
|
||||
return clubs
|
||||
|
||||
@login_required
|
||||
def search_view(request):
|
||||
return render(request, "core/search.jinja", context={'result': search(request.GET.get('query', ''))})
|
||||
result = {
|
||||
'users': search_user(request.GET.get('query', '')),
|
||||
'clubs': search_club(request.GET.get('query', '')),
|
||||
}
|
||||
return render(request, "core/search.jinja", context={'result': result})
|
||||
|
||||
@login_required
|
||||
def search_user_json(request):
|
||||
result = {
|
||||
'users': search_user(request.GET.get('query', ''), True),
|
||||
}
|
||||
return JsonResponse(result)
|
||||
|
||||
@login_required
|
||||
def search_json(request):
|
||||
return JsonResponse(search(request.GET.get('query', ''), True))
|
||||
result = {
|
||||
'users': search_user(request.GET.get('query', ''), True),
|
||||
'clubs': search_club(request.GET.get('query', ''), True),
|
||||
}
|
||||
return JsonResponse(result)
|
||||
|
||||
|
Reference in New Issue
Block a user