mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-09 19:40:19 +00:00
Merge branch 'master' into gender_options
This commit is contained in:
107
core/management/commands/check_front.py
Normal file
107
core/management/commands/check_front.py
Normal file
@ -0,0 +1,107 @@
|
||||
import re
|
||||
from subprocess import PIPE, Popen, TimeoutExpired
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
# see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
||||
# added "v?"
|
||||
semver_regex = re.compile(
|
||||
"""^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"""
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Checks the front dependencies are up to date."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
deps = settings.SITH_FRONT_DEP_VERSIONS
|
||||
|
||||
processes = dict(
|
||||
(url, create_process(url))
|
||||
for url in deps.keys()
|
||||
if parse_semver(deps[url]) is not None
|
||||
)
|
||||
|
||||
for url, process in processes.items():
|
||||
try:
|
||||
stdout, stderr = process.communicate(timeout=15)
|
||||
except TimeoutExpired:
|
||||
process.kill()
|
||||
self.stderr.write(self.style.WARNING("{}: timeout".format(url)))
|
||||
continue
|
||||
# error, notice, warning
|
||||
|
||||
stdout = stdout.decode("utf-8")
|
||||
stderr = stderr.decode("utf-8")
|
||||
|
||||
if stderr != "":
|
||||
self.stderr.write(self.style.WARNING(stderr.strip()))
|
||||
continue
|
||||
|
||||
# get all tags, parse them as semvers and find the biggest
|
||||
tags = list_tags(stdout)
|
||||
tags = map(parse_semver, tags)
|
||||
tags = filter(lambda tag: tag is not None, tags)
|
||||
latest_version = max(tags)
|
||||
|
||||
# cannot fail as those which fail are filtered in the processes dict creation
|
||||
current_version = parse_semver(deps[url])
|
||||
assert current_version is not None
|
||||
|
||||
if latest_version == current_version:
|
||||
msg = "{}: {}".format(url, semver_to_s(current_version))
|
||||
self.stdout.write(self.style.SUCCESS(msg))
|
||||
else:
|
||||
msg = "{}: {} < {}".format(
|
||||
url, semver_to_s(current_version), semver_to_s(latest_version)
|
||||
)
|
||||
self.stdout.write(self.style.ERROR(msg))
|
||||
|
||||
|
||||
def create_process(url):
|
||||
"""Spawn a "git ls-remote --tags" child process."""
|
||||
return Popen(["git", "ls-remote", "--tags", url], stdout=PIPE, stderr=PIPE)
|
||||
|
||||
|
||||
def list_tags(s):
|
||||
"""Parses "git ls-remote --tags" output. Takes a string."""
|
||||
tag_prefix = "refs/tags/"
|
||||
|
||||
for line in s.strip().split("\n"):
|
||||
# an example line could be:
|
||||
# "1f41e2293f9c3c1962d2d97afa666207b98a222a\trefs/tags/foo"
|
||||
parts = line.split("\t")
|
||||
|
||||
# check we have a commit ID (SHA-1 hash) and a tag name
|
||||
assert len(parts) == 2
|
||||
assert len(parts[0]) == 40
|
||||
assert parts[1].startswith(tag_prefix)
|
||||
|
||||
# avoid duplicates (a peeled tag will appear twice: as "name" and as "name^{}")
|
||||
if not parts[1].endswith("^{}"):
|
||||
yield parts[1][len(tag_prefix) :]
|
||||
|
||||
|
||||
def parse_semver(s):
|
||||
"""
|
||||
Turns a semver string into a 3-tuple or None if the parsing failed, it is a
|
||||
prerelease or it has build metadata.
|
||||
|
||||
See https://semver.org
|
||||
"""
|
||||
m = semver_regex.match(s)
|
||||
|
||||
if (
|
||||
m is None
|
||||
or m.group("prerelease") is not None
|
||||
or m.group("buildmetadata") is not None
|
||||
):
|
||||
return None
|
||||
|
||||
return (int(m.group("major")), int(m.group("minor")), int(m.group("patch")))
|
||||
|
||||
|
||||
def semver_to_s(t):
|
||||
"""Expects a 3-tuple with ints and turns it into a string of type "1.2.3"."""
|
||||
return "{}.{}.{}".format(t[0], t[1], t[2])
|
@ -31,7 +31,7 @@ from django.conf import settings
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
Compiles scss in static folder for production
|
||||
Compiles scss in static folder for production
|
||||
"""
|
||||
|
||||
help = "Compile scss files from static folder"
|
||||
|
@ -1492,7 +1492,9 @@ class OperationLog(models.Model):
|
||||
User, related_name="logs", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
operation_type = models.CharField(
|
||||
_("operation type"), max_length=40, choices=settings.SITH_LOG_OPERATION_TYPE,
|
||||
_("operation type"),
|
||||
max_length=40,
|
||||
choices=settings.SITH_LOG_OPERATION_TYPE,
|
||||
)
|
||||
|
||||
def is_owned_by(self, user):
|
||||
|
@ -33,16 +33,16 @@ from django.db import connection, migrations
|
||||
|
||||
class PsqlRunOnly(migrations.RunSQL):
|
||||
"""
|
||||
This is an SQL runner that will launch the given command only if
|
||||
the used DBMS is PostgreSQL.
|
||||
It may be useful to run Postgres' specific SQL, or to take actions
|
||||
that would be non-senses with backends other than Postgre, such
|
||||
as disabling particular constraints that would prevent the migration
|
||||
to run successfully.
|
||||
This is an SQL runner that will launch the given command only if
|
||||
the used DBMS is PostgreSQL.
|
||||
It may be useful to run Postgres' specific SQL, or to take actions
|
||||
that would be non-senses with backends other than Postgre, such
|
||||
as disabling particular constraints that would prevent the migration
|
||||
to run successfully.
|
||||
|
||||
See `club/migrations/0010_auto_20170912_2028.py` as an example.
|
||||
Some explanations can be found here too:
|
||||
https://stackoverflow.com/questions/28429933/django-migrations-using-runpython-to-commit-changes
|
||||
See `club/migrations/0010_auto_20170912_2028.py` as an example.
|
||||
Some explanations can be found here too:
|
||||
https://stackoverflow.com/questions/28429933/django-migrations-using-runpython-to-commit-changes
|
||||
"""
|
||||
|
||||
def _run_sql(self, schema_editor, sqls):
|
||||
|
@ -35,9 +35,9 @@ from core.scss.storage import ScssFileStorage, find_file
|
||||
|
||||
class ScssProcessor(object):
|
||||
"""
|
||||
If DEBUG mode enabled : compile the scss file
|
||||
Else : give the path of the corresponding css supposed to already be compiled
|
||||
Don't forget to use compilestatics to compile scss for production
|
||||
If DEBUG mode enabled : compile the scss file
|
||||
Else : give the path of the corresponding css supposed to already be compiled
|
||||
Don't forget to use compilestatics to compile scss for production
|
||||
"""
|
||||
|
||||
prefix = iri_to_uri(getattr(settings, "STATIC_URL", "/static/"))
|
||||
|
@ -34,6 +34,7 @@ from forum.models import ForumMessage, ForumMessageMeta
|
||||
class UserIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True)
|
||||
auto = indexes.EdgeNgramField(use_template=True)
|
||||
last_update = indexes.DateTimeField(model_attr="last_update")
|
||||
|
||||
def get_model(self):
|
||||
return User
|
||||
@ -45,6 +46,9 @@ class UserIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
def get_updated_field(self):
|
||||
return "last_update"
|
||||
|
||||
def prepare_auto(self, obj):
|
||||
return self.prepared_data["auto"].strip()[:245]
|
||||
|
||||
|
||||
class IndexSignalProcessor(signals.BaseSignalProcessor):
|
||||
def setup(self):
|
||||
|
1
core/static/core/js/vue.global.prod.js
Normal file
1
core/static/core/js/vue.global.prod.js
Normal file
File diff suppressed because one or more lines are too long
@ -28,7 +28,7 @@ $twitblue: hsl(206, 82%, 63%);
|
||||
|
||||
$shadow-color: rgb(223, 223, 223);
|
||||
|
||||
$background-bouton-color: hsl(0, 0%, 90%);
|
||||
$background-button-color: hsl(0, 0%, 95%);
|
||||
|
||||
/*--------------------------MEDIA QUERY HELPERS------------------------*/
|
||||
$small-devices: 576px;
|
||||
@ -47,10 +47,11 @@ body {
|
||||
input[type=button], input[type=submit], input[type=reset],input[type=file] {
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
background-color: $background-bouton-color;
|
||||
padding: 10px;
|
||||
background-color: $background-button-color;
|
||||
padding: 0.4em;
|
||||
margin: 0.1em;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
font-size: 1.2em;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
box-shadow: $shadow-color 0px 0px 1px;
|
||||
@ -62,9 +63,10 @@ input[type=button], input[type=submit], input[type=reset],input[type=file] {
|
||||
button{
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
background-color: $background-bouton-color;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
background-color: $background-button-color;
|
||||
padding: 0.4em;
|
||||
margin: 0.1em;
|
||||
font-size: 1.18em;
|
||||
border-radius: 5px;
|
||||
box-shadow: $shadow-color 0px 0px 1px;
|
||||
cursor: pointer;
|
||||
@ -75,24 +77,26 @@ button{
|
||||
input,textarea[type=text],[type=number]{
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
background-color: $background-bouton-color;
|
||||
padding: 7px;
|
||||
font-size: 16px;
|
||||
background-color: $background-button-color;
|
||||
padding: 0.4em;
|
||||
margin: 0.1em;
|
||||
font-size: 1.2em;
|
||||
border-radius: 5px;
|
||||
max-width: 95%;
|
||||
}
|
||||
textarea{
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
background-color: $background-bouton-color;
|
||||
background-color: $background-button-color;
|
||||
padding: 7px;
|
||||
font-size: 16px;
|
||||
font-size: 1.2em;
|
||||
border-radius: 5px;
|
||||
}
|
||||
select{
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
background-color: $background-bouton-color;
|
||||
font-size: 1.2em;
|
||||
background-color: $background-button-color;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
@ -130,9 +134,10 @@ a {
|
||||
|
||||
#header_language_chooser {
|
||||
position: absolute;
|
||||
top: 0.2em;
|
||||
right: 0.5em;
|
||||
top: 2em;
|
||||
left: 0.5em;
|
||||
width: 3%;
|
||||
min-width: 2.2em;
|
||||
text-align: center;
|
||||
input {
|
||||
display: block;
|
||||
@ -157,9 +162,6 @@ header {
|
||||
border-radius: 0px 0px 10px 10px;
|
||||
|
||||
#header_logo {
|
||||
display: inline-block;
|
||||
flex: none;
|
||||
background-size: 100% 100%;
|
||||
background-color: $white-color;
|
||||
padding: 0.2em;
|
||||
border-radius: 0px 0px 0px 9px;
|
||||
@ -169,11 +171,19 @@ header {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
max-width: 70%;
|
||||
max-height: 100%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#header_connect_links {
|
||||
margin: 0.6em 0.6em 0em auto;
|
||||
padding: 0.2em;
|
||||
color: $white-color;
|
||||
form {
|
||||
display: inline;
|
||||
@ -190,8 +200,9 @@ header {
|
||||
#header_bar {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
flex-wrap: wrap;
|
||||
width: 80%;
|
||||
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
margin: 0em 1em;
|
||||
@ -203,7 +214,6 @@ header {
|
||||
}
|
||||
|
||||
#header_bars_infos {
|
||||
width: 35ch;
|
||||
flex: initial;
|
||||
list-style-type: none;
|
||||
margin: 0.2em 0.2em;
|
||||
@ -213,12 +223,15 @@ header {
|
||||
display: inline-block;
|
||||
flex: auto;
|
||||
margin: 0.8em 0em;
|
||||
input {
|
||||
width: 14ch;
|
||||
}
|
||||
}
|
||||
|
||||
#header_user_links {
|
||||
display: flex;
|
||||
width: 120ch;
|
||||
flex: initial;
|
||||
flex-wrap: wrap;
|
||||
text-align: right;
|
||||
margin: 0em;
|
||||
div {
|
||||
@ -287,42 +300,34 @@ header {
|
||||
|
||||
#info_boxes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 90%;
|
||||
margin: 1em auto;
|
||||
p {
|
||||
margin: 0px;
|
||||
padding: 7px;
|
||||
}
|
||||
#alert_box, #info_box {
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
flex: auto;
|
||||
padding: 2px;
|
||||
margin: 0.2em 1.5%;
|
||||
min-width: 10%;
|
||||
max-width: 46%;
|
||||
min-height: 20px;
|
||||
flex: 49%;
|
||||
font-size: 0.9em;
|
||||
margin: 0.2em;
|
||||
border-radius: 0.6em;
|
||||
.markdown {
|
||||
margin: 0.5em;
|
||||
}
|
||||
&:before {
|
||||
float: left;
|
||||
font-family: FontAwesome;
|
||||
font-size: 4em;
|
||||
float: right;
|
||||
margin: 0.2em;
|
||||
}
|
||||
}
|
||||
#info_box {
|
||||
border-radius: 10px;
|
||||
background: $primary-neutral-light-color;
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
font-size: 4em;
|
||||
content: "\f05a";
|
||||
color: hsl(210, 100%, 56%);
|
||||
}
|
||||
}
|
||||
#alert_box {
|
||||
border-radius: 10px;
|
||||
background: $second-color;
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
font-size: 4em;
|
||||
content: "\f06a";
|
||||
color: $white-color;
|
||||
}
|
||||
@ -345,12 +350,12 @@ header {
|
||||
a {
|
||||
flex: auto;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
padding: 1.5em;
|
||||
color: $white-color;
|
||||
font-style: normal;
|
||||
font-weight: bolder;
|
||||
text-decoration: none;
|
||||
|
||||
|
||||
&:hover {
|
||||
background: $secondary-neutral-color;
|
||||
color: $white-color;
|
||||
@ -458,6 +463,8 @@ header {
|
||||
|
||||
/*---------------------------------NEWS--------------------------------*/
|
||||
#news {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.news_column {
|
||||
display: inline-block;
|
||||
margin: 0px;
|
||||
@ -467,11 +474,13 @@ header {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#right_column {
|
||||
width: 20%;
|
||||
flex: 20%;
|
||||
float: right;
|
||||
margin: 0.2em;
|
||||
}
|
||||
#left_column {
|
||||
width: 79%;
|
||||
flex: 79%;
|
||||
margin: 0.2em;
|
||||
h3 {
|
||||
background: $second-color;
|
||||
box-shadow: $shadow-color 1px 1px 1px;
|
||||
@ -484,6 +493,11 @@ header {
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: $small-devices){
|
||||
#left_column, #right_column {
|
||||
flex: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* AGENDA/BIRTHDAYS */
|
||||
#agenda,#birthdays {
|
||||
@ -691,6 +705,12 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-devices){
|
||||
#page {
|
||||
width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
#news_details {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
@ -723,7 +743,7 @@ header {
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
font-size: 1.2em;
|
||||
border-radius: 2px;
|
||||
float: right;
|
||||
display: block;
|
||||
@ -1111,33 +1131,36 @@ u, .underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#basket {
|
||||
width: 40%;
|
||||
background: $primary-neutral-light-color;
|
||||
float: right;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#products {
|
||||
width: 90%;
|
||||
margin: 0px auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#bar_ui {
|
||||
float: left;
|
||||
min-width: 57%;
|
||||
}
|
||||
padding: 0.4em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
#user_info_container {}
|
||||
#products {
|
||||
flex-basis: 100%;
|
||||
margin: 0.2em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#user_info {
|
||||
float: right;
|
||||
padding: 5px;
|
||||
width: 40%;
|
||||
margin: 0px auto;
|
||||
background: $secondary-neutral-light-color;
|
||||
#click_form {
|
||||
flex: auto;
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
#user_info {
|
||||
flex: auto;
|
||||
padding: 0.5em;
|
||||
margin: 0.2em;
|
||||
height: 100%;
|
||||
background: $secondary-neutral-light-color;
|
||||
img {
|
||||
max-width: 70%;
|
||||
}
|
||||
input {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------USER PROFILE----------------------------*/
|
||||
@ -1212,6 +1235,11 @@ u, .underline {
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: $small-devices){
|
||||
#user_profile_infos, #user_profile_pictures {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1412,6 +1440,7 @@ textarea {
|
||||
.search_bar {
|
||||
margin: 10px 0px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 20p;
|
||||
align-items: center;
|
||||
}
|
||||
@ -1551,6 +1580,7 @@ footer {
|
||||
color: $white-color;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: $primary-neutral-dark-color;
|
||||
box-shadow: $shadow-color 0px 0px 15px;
|
||||
a {
|
||||
@ -2181,4 +2211,4 @@ $pedagogy-white-text: #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
<head>
|
||||
{% block head %}
|
||||
<title>{% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="{{ static('core/img/favicon.ico') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/base.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/jquery.datetimepicker.min.css') }}">
|
||||
@ -27,6 +28,7 @@
|
||||
<!-- BEGIN HEADER -->
|
||||
{% block header %}
|
||||
{% if not popup %}
|
||||
<header>
|
||||
<div id="header_language_chooser">
|
||||
{% for language in LANGUAGES %}
|
||||
<form action="{{ url('set_language') }}" method="post">{% csrf_token %}
|
||||
@ -37,10 +39,11 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<header>
|
||||
{% if not user.is_authenticated %}
|
||||
<div id="header_logo" style="background-image: url('{{ static('core/img/logo.png') }}'); width: 185px; height: 100px;">
|
||||
<a href="{{ url('core:index') }}"></a>
|
||||
<div id="header_logo">
|
||||
<a href="{{ url('core:index') }}">
|
||||
<img src="{{ static('core/img/logo.png') }}" alt="AE logo">
|
||||
</a>
|
||||
</div>
|
||||
<div id="header_connect_links">
|
||||
<form method="post" action="{{ url('core:login') }}">
|
||||
@ -54,12 +57,14 @@
|
||||
<a href="{{ url('core:register') }}"><button type="button">{% trans %}Register{% endtrans %}</button></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="header_logo" style="background-image: url('{{ static('core/img/logo.png') }}'); width: 92px; height: 52px;">
|
||||
<a href="{{ url('core:index') }}"></a>
|
||||
<div id="header_logo">
|
||||
<a href="{{ url('core:index') }}">
|
||||
<img src="{{ static('core/img/logo.png') }}" alt="AE logo">
|
||||
</a>
|
||||
</div>
|
||||
<div id="header_bar">
|
||||
<ul id="header_bars_infos">
|
||||
{% cache 100 counters_activity %}
|
||||
{% cache 100 "counters_activity" %}
|
||||
{% for bar in Counter.objects.filter(type="BAR").all() %}
|
||||
<li>
|
||||
<a href="{{ url('counter:activity', counter_id=bar.id) }}" style="padding: 0px">
|
||||
@ -85,7 +90,7 @@
|
||||
<a href="{{ url('core:user_profile', user_id=user.id) }}">{{ user.get_display_name() }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#" onclick="display_notif()"><i class="fa fa-bell-o"></i> ({{ user.notifications.filter(viewed=False).count() }})</a>
|
||||
<a href="#" onclick="display_notif()" style="white-space: nowrap;"><i class="fa fa-bell-o"></i> ({{ user.notifications.filter(viewed=False).count() }})</a>
|
||||
<ul id="header_notif">
|
||||
{% for n in user.notifications.filter(viewed=False).order_by('-date') %}
|
||||
<li>
|
||||
@ -126,17 +131,19 @@
|
||||
</header>
|
||||
|
||||
<div id="info_boxes">
|
||||
{% set sith = get_sith() %}
|
||||
{% if sith.alert_msg %}
|
||||
<div id="alert_box">
|
||||
{{ sith.alert_msg|markdown }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if sith.info_msg %}
|
||||
<div id="info_box">
|
||||
{{ sith.info_msg|markdown }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block info_boxes %}
|
||||
{% set sith = get_sith() %}
|
||||
{% if sith.alert_msg %}
|
||||
<div id="alert_box">
|
||||
{{ sith.alert_msg|markdown }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if sith.info_msg %}
|
||||
<div id="info_box">
|
||||
{{ sith.info_msg|markdown }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% else %}{# if not popup #}
|
||||
|
@ -4,6 +4,12 @@
|
||||
{% trans %}Delete confirmation{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block info_boxes %}
|
||||
{% endblock %}
|
||||
|
||||
{% block nav %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans %}Delete confirmation{% endtrans %}</h2>
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
|
@ -52,6 +52,7 @@
|
||||
{% if not form.instance.profile_pict %}
|
||||
<script src="{{ static('core/js/webcam.js') }}"></script>
|
||||
<script language="JavaScript">
|
||||
Webcam.on('error', function(msg) { console.log('Webcam.js error: ' + msg) })
|
||||
Webcam.set({
|
||||
width: 320,
|
||||
height: 240,
|
||||
|
@ -12,7 +12,7 @@
|
||||
{% for picture in pictures[a.id] %}
|
||||
<div class="picture">
|
||||
<a href="{{ url("sas:picture", picture_id=picture.id) }}#pict">
|
||||
<img src="{{ picture.get_download_thumb_url() }}" alt="{{ picture.get_display_name() }}" style="max-width: 100%"/>
|
||||
<img src="{{ picture.get_download_thumb_url() }}" alt="{{ picture.get_display_name() }}" style="max-width: 100%" loading="lazy"/>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -1,3 +1,13 @@
|
||||
{{ object.first_name }}
|
||||
{{ object.last_name }}
|
||||
{{ object.nick_name }}
|
||||
{% load search_helpers %}
|
||||
|
||||
{% with first=object.first_name|safe|slugify last=object.last_name|safe|slugify nick=object.nick_name|default_if_none:""|safe|slugify %}
|
||||
|
||||
{{ first|replace:"|-| " }}
|
||||
{{ last|replace:"|-| " }}
|
||||
{{ nick|replace:"|-| " }}
|
||||
{% if first|count:"-" != 0 %}{{ first|cut:"-" }}{% endif %}
|
||||
{% if last|count:"-" != 0 %}{{ last|cut:"-" }}{% endif %}
|
||||
{% if nick|count:"-" != 0 %}{{ nick|cut:"-" }}{% endif %}
|
||||
{{ first|cut:"-" }}{{ last|cut:"-" }}
|
||||
|
||||
{% endwith %}
|
||||
|
@ -94,7 +94,7 @@ def datetime_format_python_to_PHP(python_format_string):
|
||||
@register.simple_tag()
|
||||
def scss(path):
|
||||
"""
|
||||
Return path of the corresponding css file after compilation
|
||||
Return path of the corresponding css file after compilation
|
||||
"""
|
||||
processor = ScssProcessor(path)
|
||||
return processor.get_converted_scss()
|
||||
|
27
core/templatetags/search_helpers.py
Normal file
27
core/templatetags/search_helpers.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django.template.exceptions import TemplateSyntaxError
|
||||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
# arg should be of the form "|foo|bar" where the first character is the
|
||||
# separator between old and new in value.replace(old, new)
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def replace(value, arg):
|
||||
# s.replace('', '') == s so len(arg) == 2 is fine
|
||||
if len(arg) < 2:
|
||||
raise TemplateSyntaxError("badly formatted argument")
|
||||
|
||||
arg = arg.split(arg[0])
|
||||
|
||||
if len(arg) != 3:
|
||||
raise TemplateSyntaxError("badly formatted argument")
|
||||
|
||||
return value.replace(arg[1], arg[2])
|
||||
|
||||
|
||||
@register.filter
|
||||
def count(value, arg):
|
||||
return value.count(arg)
|
@ -344,19 +344,19 @@ class QuickNotifMixin:
|
||||
|
||||
class DetailFormView(SingleObjectMixin, FormView):
|
||||
"""
|
||||
Class that allow both a detail view and a form view
|
||||
Class that allow both a detail view and a form view
|
||||
"""
|
||||
|
||||
def get_object(self):
|
||||
"""
|
||||
Get current group from id in url
|
||||
Get current group from id in url
|
||||
"""
|
||||
return self.cached_object
|
||||
|
||||
@cached_property
|
||||
def cached_object(self):
|
||||
"""
|
||||
Optimisation on group retrieval
|
||||
Optimisation on group retrieval
|
||||
"""
|
||||
return super(DetailFormView, self).get_object()
|
||||
|
||||
|
@ -42,6 +42,10 @@ from django.utils.translation import ugettext
|
||||
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
|
||||
from ajax_select.fields import AutoCompleteSelectField
|
||||
from ajax_select import make_ajax_field
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from django.utils import timezone
|
||||
import datetime
|
||||
from django.forms.utils import to_current_timezone
|
||||
|
||||
import re
|
||||
|
||||
@ -114,14 +118,11 @@ class SelectFile(TextInput):
|
||||
attrs["class"] = "select_file"
|
||||
else:
|
||||
attrs = {"class": "select_file"}
|
||||
output = (
|
||||
'%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>'
|
||||
% {
|
||||
"content": super(SelectFile, self).render(name, value, attrs, renderer),
|
||||
"title": _("Choose file"),
|
||||
"name": name,
|
||||
}
|
||||
)
|
||||
output = '%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>' % {
|
||||
"content": super(SelectFile, self).render(name, value, attrs, renderer),
|
||||
"title": _("Choose file"),
|
||||
"name": name,
|
||||
}
|
||||
output += (
|
||||
'<span name="'
|
||||
+ name
|
||||
@ -138,14 +139,11 @@ class SelectUser(TextInput):
|
||||
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, renderer),
|
||||
"title": _("Choose user"),
|
||||
"name": name,
|
||||
}
|
||||
)
|
||||
output = '%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>' % {
|
||||
"content": super(SelectUser, self).render(name, value, attrs, renderer),
|
||||
"title": _("Choose user"),
|
||||
"name": name,
|
||||
}
|
||||
output += (
|
||||
'<span name="'
|
||||
+ name
|
||||
@ -399,3 +397,26 @@ class GiftForm(forms.ModelForm):
|
||||
id=user_id
|
||||
)
|
||||
self.fields["user"].widget = forms.HiddenInput()
|
||||
|
||||
|
||||
class TzAwareDateTimeField(forms.DateTimeField):
|
||||
def __init__(
|
||||
self, input_formats=["%Y-%m-%d %H:%M:%S"], widget=SelectDateTime, **kwargs
|
||||
):
|
||||
super().__init__(input_formats=input_formats, widget=widget, **kwargs)
|
||||
|
||||
def prepare_value(self, value):
|
||||
# the db value is a datetime as a string in UTC
|
||||
if isinstance(value, str):
|
||||
# convert it into a naive datetime (no timezone attached)
|
||||
value = parse_datetime(value)
|
||||
# attach it to the UTC timezone (so that to_current_timezone()
|
||||
# converts it to the local timezone)
|
||||
value = timezone.make_aware(value, timezone.utc)
|
||||
|
||||
if isinstance(value, datetime.datetime):
|
||||
value = to_current_timezone(value)
|
||||
# otherwise it is formatted according to locale (in french)
|
||||
value = str(value)
|
||||
|
||||
return value
|
||||
|
@ -44,7 +44,7 @@ from core.views import CanEditMixin, DetailFormView
|
||||
|
||||
class EditMembersForm(forms.Form):
|
||||
"""
|
||||
Add and remove members from a Group
|
||||
Add and remove members from a Group
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -66,7 +66,7 @@ class EditMembersForm(forms.Form):
|
||||
|
||||
def clean_users_added(self):
|
||||
"""
|
||||
Check that the user is not trying to add an user already in the group
|
||||
Check that the user is not trying to add an user already in the group
|
||||
"""
|
||||
cleaned_data = super(EditMembersForm, self).clean()
|
||||
users_added = cleaned_data.get("users_added", None)
|
||||
@ -100,7 +100,7 @@ class GroupListView(CanEditMixin, ListView):
|
||||
|
||||
class GroupEditView(CanEditMixin, UpdateView):
|
||||
"""
|
||||
Edit infos of a Group
|
||||
Edit infos of a Group
|
||||
"""
|
||||
|
||||
model = RealGroup
|
||||
@ -111,7 +111,7 @@ class GroupEditView(CanEditMixin, UpdateView):
|
||||
|
||||
class GroupCreateView(CanEditMixin, CreateView):
|
||||
"""
|
||||
Add a new Group
|
||||
Add a new Group
|
||||
"""
|
||||
|
||||
model = RealGroup
|
||||
@ -121,8 +121,8 @@ class GroupCreateView(CanEditMixin, CreateView):
|
||||
|
||||
class GroupTemplateView(CanEditMixin, DetailFormView):
|
||||
"""
|
||||
Display all users in a given Group
|
||||
Allow adding and removing users from it
|
||||
Display all users in a given Group
|
||||
Allow adding and removing users from it
|
||||
"""
|
||||
|
||||
model = RealGroup
|
||||
@ -156,7 +156,7 @@ class GroupTemplateView(CanEditMixin, DetailFormView):
|
||||
|
||||
class GroupDeleteView(CanEditMixin, DeleteView):
|
||||
"""
|
||||
Delete a Group
|
||||
Delete a Group
|
||||
"""
|
||||
|
||||
model = RealGroup
|
||||
|
@ -30,6 +30,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.utils import html
|
||||
from django.views.generic import ListView, TemplateView
|
||||
from django.conf import settings
|
||||
from django.utils.text import slugify
|
||||
|
||||
import json
|
||||
|
||||
@ -73,7 +74,18 @@ def notification(request, notif_id):
|
||||
|
||||
def search_user(query, as_json=False):
|
||||
try:
|
||||
res = SearchQuerySet().models(User).autocomplete(auto=html.escape(query))[:20]
|
||||
# slugify turns everything into ascii and every whitespace into -
|
||||
# it ends by removing duplicate - (so ' - ' will turn into '-')
|
||||
# replace('-', ' ') because search is whitespace based
|
||||
query = slugify(query).replace("-", " ")
|
||||
# TODO: is this necessary?
|
||||
query = html.escape(query)
|
||||
res = (
|
||||
SearchQuerySet()
|
||||
.models(User)
|
||||
.autocomplete(auto=query)
|
||||
.order_by("-last_update")[:20]
|
||||
)
|
||||
return [r.object for r in res]
|
||||
except TypeError:
|
||||
return []
|
||||
|
Reference in New Issue
Block a user