Cropper bei Profilen hinzugefügt

This commit is contained in:
holger.trampe 2020-01-19 22:41:23 +01:00
parent 6f4544af64
commit f6f4b8f578
16 changed files with 3880 additions and 59 deletions

View File

@ -12,7 +12,6 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -120,6 +119,7 @@ CKEDITOR_CONFIGS = {
}
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',

View File

@ -5,16 +5,14 @@
<div class="content-section">
<h3>{{request.user.profile.agency.name}}</h3>
<hr>
<h4>Organigramm</h4>
<h4>Organigramm</h4>
{% if invisible_users > 0%}
<div class="alert alert-danger" role="alert">
Achtung! {{invisible_users}} Mitarbeiter sind anderen Mitarbeitern untergeordnet, die nicht im Organigramm sichtbar sein sollen. Bitte prüfen sie die Zuordnung der übergeordneten Mitarbeiter!
</div>
{% endif %}
<div class="d-flex justify-content-center">
<div id="diagram"></div>
</div>
<div id="spinner" class="text-center" style="margin-top: 15%; width: 100%; height: 100%; display: none;margin-left:auto;
margin-right:auto;">
<div class="spinner-border text-danger" role="status">
<span class="sr-only">Loading...</span>
</div>
<div id="diagram"></div>
</div>
</div>
@ -22,9 +20,6 @@ margin-right:auto;">
<script type="text/javascript">
var data = [
{ 'id' : '{{request.user.profile.agency.pk}}', 'parent' : "", 'role': "", 'name': '{{request.user.profile.agency.name}}', 'color': '#71AF17', "imageUrl": "", 'children' : []},
{% for u in agencyuser %}
@ -36,7 +31,6 @@ var data = [
{% endfor %}
];
//Check for Agency and User-IDs - dont touch the userid for URLs!
for(i = 0; i < data.length; i++){
for(k = 0; k < data.length; k++){
@ -49,7 +43,6 @@ for(i = 0; i < data.length; i++){
data[m]['parent'] = data[m]['parent']*10;
}
}
}
}
}
@ -71,13 +64,13 @@ function unflatten(arr) {
for (var id in mappedArr) {
if (mappedArr.hasOwnProperty(id)) {
mappedElem = mappedArr[id];
mappedElem = mappedArr[id];
// If the element is not at the root level, add it to its parent array of children.
if (mappedElem.parent) {
mappedArr[mappedElem['parent']]['children'].push(mappedElem);
}
// If the element is at the root level, add it to first level elements array.
else {
else {
tree.push(mappedElem);
}
}
@ -90,7 +83,7 @@ var html = ['<ul class="tree">'];
//Create UL-LI-List for tree
function createList(arr) {
html.push('<ul>');
$.each(arr, function(i, val) {
$.each(arr, function(i, val) {
if(val.parent == ""){
html.push('<li><a href="#"><h4>' + val.name + "</h4></a>");
}
@ -100,7 +93,7 @@ function createList(arr) {
if (val.children) {
createList(val.children)
}
html.push('</li>');
html.push('</li>');
});
html.push('</ul>');
}
@ -115,7 +108,6 @@ for(i = 0; i < html.length; i++){
}
}
}
function goToUser(id){
window.location.href = "/orga/single/"+id;
}

View File

@ -9,27 +9,23 @@ import webcolors
@login_required
def mainorga(request):
#leader = list(User.objects.filter(profile__agency__pk=request.user.profile.agency.pk).filter(profile__func='lead'))
#if len(leader) > 0:
# leader = leader[0]
#else:
# leader = None
agencyuser = list(User.objects.filter(profile__agency__pk=request.user.profile.agency.pk).filter(profile__visible=True).order_by('-id'))
nonvisibleuser = list(User.objects.filter(profile__agency__pk=request.user.profile.agency.pk).filter(profile__visible=False).order_by('-id'))
invisible_users = 0;
# Check, if parented users are invisible. Remove them and give user an info!
for ele in nonvisibleuser:
for vis in agencyuser:
if vis.profile.parent.profile.pk == ele.pk:
agencyuser.remove(vis)
invisible_users += 1
#indoor = list(User.objects.filter(profile__agency__pk=request.user.profile.agency.pk).filter(profile__func='indoor'))
#external = list(User.objects.filter(profile__agency__pk=request.user.profile.agency.pk).filter(profile__func='external'))
#trainee = list(User.objects.filter(profile__agency__pk=request.user.profile.agency.pk).filter(profile__func='trainee'))
context = {
'active_link' : 'orga',
'agencyuser' : agencyuser
'agencyuser' : agencyuser,
'invisible_users' : invisible_users
}
# 'leader' : leader,
# 'indoor' : indoor,
# 'external' : external,
# 'trainee' : trainee
return render(request, 'orga/orga_main.html', context)

View File

@ -66,6 +66,8 @@ Klasse für die Zusatzinfos eines Nutzers.
- Aufgaben
'''
class Profile(models.Model):
# AGENCY TASKS
@ -88,7 +90,6 @@ class Profile(models.Model):
image = models.ImageField(default='userprofilepics/default.jpg', upload_to='userprofilepics', blank=True)
compfunc = models.CharField(max_length=60, blank=True)
visible = models.BooleanField(default=True)
def __str__(self):
return f'{self.user.last_name}'
@ -98,7 +99,7 @@ class Profile(models.Model):
return reverse('users-update', kwargs={'pk':self.pk})
# Erst Oberklasse speichern, dann Bild verkleinern
'''
def save(self, **kwargs):
super().save()
if self.image:
@ -114,7 +115,7 @@ class Profile(models.Model):
wsize = int((float(img.size[0]) * float(hpercent)))
img = img.resize((wsize, baseheight), Image.ANTIALIAS)
img.save(self.image.path)
'''
@property
def get_photo_url(self):
if self.image and hasattr(self.image, 'url'):
@ -124,7 +125,8 @@ class Profile(models.Model):
# PERMISSIONS - Über alle Modelle hinweg, in der url.py wird dann die route verhindert!
# Im template: if perms.users.PERMISSION
class Meta:
class Meta:
permissions = [
('agency_change', 'Agenturinformationen verändern'),
('users_usermanagement', 'Benutzer bearbeiten'),
@ -136,3 +138,4 @@ class Profile(models.Model):
]

View File

@ -0,0 +1,9 @@
/*!
* Cropper.js v1.5.6
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2019-10-04T04:33:44.164Z
*/.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}

3616
users/static/users/js/cropper.min.js vendored Normal file

File diff suppressed because it is too large Load Diff

73
users/static/users/js/jquery-cropper.js vendored Normal file
View File

@ -0,0 +1,73 @@
/*!
* jQuery Cropper v1.0.1
* https://fengyuanchen.github.io/jquery-cropper
*
* Copyright 2018-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2019-10-19T08:48:33.062Z
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery'), require('cropperjs')) :
typeof define === 'function' && define.amd ? define(['jquery', 'cropperjs'], factory) :
(global = global || self, factory(global.jQuery, global.Cropper));
}(this, function ($, Cropper) { 'use strict';
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
Cropper = Cropper && Cropper.hasOwnProperty('default') ? Cropper['default'] : Cropper;
if ($ && $.fn && Cropper) {
var AnotherCropper = $.fn.cropper;
var NAMESPACE = 'cropper';
$.fn.cropper = function jQueryCropper(option) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var result;
this.each(function (i, element) {
var $element = $(element);
var isDestroy = option === 'destroy';
var cropper = $element.data(NAMESPACE);
if (!cropper) {
if (isDestroy) {
return;
}
var options = $.extend({}, $element.data(), $.isPlainObject(option) && option);
cropper = new Cropper(element, options);
$element.data(NAMESPACE, cropper);
}
if (typeof option === 'string') {
var fn = cropper[option];
if ($.isFunction(fn)) {
result = fn.apply(cropper, args);
if (result === cropper) {
result = undefined;
}
if (isDestroy) {
$element.removeData(NAMESPACE);
}
}
}
});
return result !== undefined ? result : this;
};
$.fn.cropper.Constructor = Cropper;
$.fn.cropper.setDefaults = Cropper.setDefaults;
$.fn.cropper.noConflict = function noConflict() {
$.fn.cropper = AnotherCropper;
return this;
};
}
}));

View File

@ -13,7 +13,7 @@
<!-- Custom fonts for this template-->
<link rel="canonical" href="https://www.digitale-agentur.com">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<link href="{%static 'users/vendor/fontawesome-free/css/all.min.css' %}" rel="stylesheet" type="text/css">
@ -32,6 +32,9 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote.js"></script>
<!-- CROPPER -->
<link href="{% static 'users/css/cropper.min.css' %}" rel="stylesheet">
<!-- TABLE SORT -->
<!--<link rel="stylesheet" href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.min.css">-->
@ -331,6 +334,8 @@
<!-- CUSTOM FONT -->
<link href="{% static 'users/css/custom.css' %}" rel="stylesheet">
<!-- TABLE SORT -->
<!--<script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>-->

View File

@ -1,11 +1,14 @@
{% extends "users/base.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<script src="{% static 'users/js/cropper.min.js' %}"></script>
<script src="{% static 'users/js/jquery-cropper.js' %}"></script>
<div class="content-section">
<div class="media">
<img class="img-profile " width="17%" src="{{ prof_user.profile.get_photo_url }}">
<img class="img-profile " id="profpic" width="17%" src="{{ prof_user.profile.get_photo_url }}">
<div class="media-body col-5">
<h2 class="account-heading">Profil von {{ prof_user.first_name }} {{ prof_user.last_name }}</h2>
<h2 class="account-heading">Profil von {{ prof_user.first_name }} {{ prof_user.last_name }}</h2>
<hr>
<div class="row mt-2">
<div class="col-md-6">
@ -41,13 +44,13 @@
</div>
<!-- Für das Speichern der Bilder enctype -->
<div class="col-7 mt-5">
<form method="POST" enctype="multipart/form-data">
<form method="POST" id="newprofiledata" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">
Profil bearbeiten
</legend>
<!-- FORMS LADEN -->
<!-- FORMS LADEN -->
{{ profileform_form|crispy }}
<button type="button" id="" onclick="javascript:sendPassMail({{prof_user.pk}})" class="btn btn-success">E-Mail mit Link zur Passworterstellung senden</button>&nbsp;&nbsp;<span class="alert alert-success" id="mailsend" role="alert" style="display: none;">&nbsp;E-Mail gesendet!</span>
</fieldset>
@ -63,7 +66,7 @@
<option id="{{us.pk}}" value="{{us.first_name}} {{us.last_name}}"></option>
{% endfor %}
</datalist>
</div>
<hr>
<div class="form-group">
@ -73,6 +76,111 @@
</form>
</div>
</div>
<!-- MODAL TO CROP THE IMAGE -->
<!-- MODAL TO CROP THE IMAGE -->
<div class="modal fade " id="modalCrop">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Bereich bestimmen</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="clearImgField()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="imgmodbody">
<img src="" id="imagemod" style="max-width: 100%; max-height: 100%;">
</div>
<div class="modal-footer">
<div class="btn-group pull-left" role="group">
<button type="button" class="btn btn-default js-zoom-in">
<span class="glyphicon glyphicon-zoom-in"></span>
</button>
<button type="button" class="btn btn-default js-zoom-out">
<span class="glyphicon glyphicon-zoom-out"></span>
</button>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal" onclick="clearImgField()">Abbrechen</button>
<button type="button" class="btn btn-primary js-crop-and-upload">Ausschneiden</button>
</div>
</div>
</div>
</div>
{% block javascript %}
<script>
$("#id_x").val(0);
$("#id_y").val(0);
$("#id_width").val($("#profpic")[0]['naturalWidth']);
$("#id_height").val($("#profpic")[0]['naturalHeight']);
function clearImgField(){
$("#id_image").val("");
}
/* SCRIPT TO OPEN THE MODAL WITH THE PREVIEW */
$("#id_image").change(function () {
if (this.files && this.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$("#imagemod").attr("src", e.target.result);
$("#modalCrop").modal("show");
}
reader.readAsDataURL(this.files[0]);
}
});
var cropBoxData;
var canvasData;
var $image = $("#imagemod");
$("#modalCrop").on("shown.bs.modal", function () {
$image.cropper({
viewMode: 3,
aspectRatio: 1/1,
strict: false,
cropBoxMovable: true,
cropBoxResizable: true,
minCropBoxWidth: 200,
minCropBoxHeight: 200,
ready: function () {
$image.cropper("setCanvasData", canvasData);
$image.cropper("setCropBoxData", cropBoxData);
}
});
$("#imgmodbody").css({
"maxWidth": 465
});
}).on("hidden.bs.modal", function () {
cropBoxData = $image.cropper("getCropBoxData");
canvasData = $image.cropper("getCanvasData");
$image.cropper("destroy");
});
$(".js-zoom-in").click(function () {
$image.cropper("zoom", 0.1);
});
$(".js-zoom-out").click(function () {
$image.cropper("zoom", -0.1);
});
/* SCRIPT TO COLLECT THE DATA AND POST TO THE SERVER */
$(".js-crop-and-upload").click(function () {
var cropData = $image.cropper("getData");
$("#id_x").val(cropData["x"]);
$("#id_y").val(cropData["y"]);
$("#id_height").val(cropData["height"]);
$("#id_width").val(cropData["width"]);
$("#id_image").attr("src", $image);
$("#modalCrop").modal('toggle');
});
</script>
{% endblock %}
<script type="text/javascript">
var ua = window.navigator.userAgent;
var isIE = /MSIE|Trident/.test(ua);

View File

@ -6,6 +6,8 @@ from .models import Profile, Agency
from django.contrib.auth.models import Permission
from areas.models import Areas
from tasks.models import Tasks
from PIL import Image
# Standard-User-Formular - NUR Username und Password wird hier genutzt
class UsersAddNewUser(UserCreationForm):
@ -25,9 +27,13 @@ class UsersChangeProfil(forms.ModelForm):
# Formular zum hinzufügen neuer Agentur-Mitglieder
class UsersAddProfileForm(forms.ModelForm):
x = forms.FloatField(widget=forms.HiddenInput())
y = forms.FloatField(widget=forms.HiddenInput())
width = forms.FloatField(widget=forms.HiddenInput())
height = forms.FloatField(widget=forms.HiddenInput())
class Meta:
model = Profile
model = Profile
labels = {
"phoneland" : "Telefon",
"phonemobile" : "Mobil",
@ -36,7 +42,23 @@ class UsersAddProfileForm(forms.ModelForm):
"image" : "Profilbild",
"visible" : "Im Organigramm sichtbar"
}
fields = ['phoneland','phonemobile', 'visible', 'func', 'compfunc', 'image']
fields = ['phoneland','phonemobile', 'visible', 'func', 'compfunc', 'image', 'x', 'y', 'width', 'height']
def save(self):
photo = super(UsersAddProfileForm, self).save()
try:
x = self.cleaned_data.get('x')
y = self.cleaned_data.get('y')
w = self.cleaned_data.get('width')
h = self.cleaned_data.get('height')
image = Image.open(photo.image)
cropped_image = image.crop((x, y, w+x, h+y))
resized_image = cropped_image.resize((560, 560), Image.ANTIALIAS)
resized_image.save(photo.image.path)
return photo
except:
print("no photo")
# Formular zum hinzufügen neuer Agentur-Mitglieder
class AgencyUpdateForm(forms.ModelForm):

View File

@ -24,7 +24,7 @@ from PIL import Image
from django.template.loader import render_to_string
from django.contrib.auth.forms import PasswordResetForm
from django.template.loader import render_to_string
from io import StringIO
'''
DASHBOARD-View
@ -201,9 +201,6 @@ class UsersPermUpdateView(LoginRequiredMixin, View):
messages.success(request, f'Berechtigungen für {user_tochange.first_name} {user_tochange.last_name} aktualisiert!')
return HttpResponseRedirect('/dashboard/usersman/')
# Benutzerprofil wird aktualisiert
@login_required
def ProfileUpdateView(request, pk):
@ -212,20 +209,20 @@ def ProfileUpdateView(request, pk):
if request.method == 'POST':
profileform_form = UsersAddProfileForm(request.POST, request.FILES, instance=prof_user.profile)
#profileform_parents = UsersAddProfileFormParents(request.POST, instance=request.user)
prename = prof_user.first_name
name = prof_user.last_name
if profileform_form.is_valid():
profileform_form.save()
prename = prof_user.first_name
name = prof_user.last_name
messages.success(request, f'Daten für {prename} {name} aktualisiert!')
# Daten neu laden und nicht die "Mächten sie die Daten speichern...?"
messages.success(request, f'Daten für {prename} {name} aktualisiert!')
return redirect('users-management')
else:
# Form in Klammern sind die aktuellen Daten :)
profileform_form = UsersAddProfileForm(instance=prof_user.profile)
possible_users = User.objects.filter(profile__agency__pk=prof_user.profile.agency.pk)
# Nur User, die im Organigramm auch sichtbar sein, können ausgewählt werden
possible_users = User.objects.filter(profile__agency__pk=prof_user.profile.agency.pk).filter(profile__visible=True)
context = {
'prof_user' : prof_user,