11 Commits

Author SHA1 Message Date
Oliver Falk
b8292b5404 Merge branch 'devel' into 'master'
Release 1.7.0

See merge request oliver/ivatar!219
2022-12-06 18:06:33 +00:00
Oliver Falk
f359532c30 Merge branch 'devel' into 'master'
Include fix for #89

See merge request oliver/ivatar!215
2022-11-17 11:39:12 +00:00
Oliver Falk
a76d5b9225 Merge branch 'devel' into 'master'
Merge latest devel tree

See merge request oliver/ivatar!214
2022-11-17 10:54:20 +00:00
Oliver Falk
71d69dde53 Merge branch 'devel' into 'master'
v1.6.2

See merge request oliver/ivatar!213
2022-10-27 07:21:23 +00:00
Oliver Falk
0b5271424c Merge branch 'devel' into 'master'
v1.6.1: New trusted URLs handling + update security page

See merge request oliver/ivatar!212
2022-09-15 17:16:14 +00:00
Oliver Falk
a492995836 Merge branch 'devel' into 'master'
Quick fix: Add www.gravatar.com to the list of trusted URIs

See merge request oliver/ivatar!206
2022-07-15 13:21:00 +00:00
Oliver Falk
ad39324650 Merge branch 'devel' into 'master'
Additional tests to increase coverage (a bit)

See merge request oliver/ivatar!205
2022-06-28 08:57:20 +00:00
Oliver Falk
ef02feed3b Merge branch 'devel' into 'master'
Typo fix + coverage adaption

See merge request oliver/ivatar!204
2022-06-21 12:27:49 +00:00
Oliver Falk
1a10861d2f Merge branch 'devel' into 'master'
Update pre-commit + new stats

See merge request oliver/ivatar!203
2022-05-03 12:24:04 +00:00
Oliver Falk
b64f939344 Merge branch 'devel' into 'master'
Enhance stats + add tests

See merge request oliver/ivatar!201
2022-02-18 13:06:29 +00:00
Oliver Falk
ddaf6a6d8a Enhance stats + add tests 2022-02-18 13:06:29 +00:00
9 changed files with 3 additions and 456 deletions

4
.env
View File

@@ -7,7 +7,3 @@ if [ -f .virtualenv/bin/activate ]; then
source .virtualenv/bin/activate
AUTOENV_ENABLE_LEAVE=True
fi
if [ -f .env.local ]; then
source .env.local
fi

1
.gitignore vendored
View File

@@ -20,4 +20,3 @@ falko_gravatar.jpg
*.egg-info
dump_all*.sql
dist/
.env.local

View File

@@ -24,7 +24,7 @@ test_and_coverage:
- echo "CACHES['default'] = CACHES['filesystem']" >> config_local.py
- python manage.py collectstatic --noinput
- coverage run --source . manage.py test -v3
- coverage report --fail-under=69
- coverage report --fail-under=70
- coverage html
artifacts:
paths:

View File

@@ -9,7 +9,7 @@ repos:
- id: prettier
files: \.(css|js|md|markdown|json)
- repo: https://github.com/python/black
rev: 22.12.0
rev: 22.10.0
hooks:
- id: black
- repo: https://github.com/pre-commit/pre-commit-hooks

View File

@@ -1,196 +0,0 @@
# -*- coding: utf-8 -*-
"""
View classes for the avatar creator
"""
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.views.generic.base import View, TemplateView
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse_lazy
from jinja2 import Environment, PackageLoader
import py_avataaars as pa
@method_decorator(login_required, name="dispatch")
class AvatarCreatorView(TemplateView):
"""
View class responsible for handling avatar creation
"""
template_name = "avatar_creator.html"
def get(self, request, *args, **kwargs):
"""
Handle get for create view
"""
if request.user:
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse_lazy("profile"))
return super().get(self, request, args, kwargs)
def get_context_data(self, **kwargs):
"""
Provide additional context data
"""
context = super().get_context_data(**kwargs)
context["SkinColor"] = list(pa.SkinColor)
context["HairColor"] = list(pa.HairColor)
context["FacialHairType"] = list(pa.FacialHairType)
context["TopType"] = list(pa.TopType)
context["HatColor"] = list(pa.Color)
context["MouthType"] = list(pa.MouthType)
context["EyesType"] = list(pa.EyesType)
context["EyebrowType"] = list(pa.EyebrowType)
context["NoseType"] = list(pa.NoseType)
context["AccessoriesType"] = list(pa.AccessoriesType)
context["ClotheType"] = list(pa.ClotheType)
context["ClotheColor"] = list(pa.Color)
context["ClotheGraphicType"] = list(pa.ClotheGraphicType)
return context
@method_decorator(login_required, name="dispatch")
class AvatarView(View):
"""
View class responsible for handling avatar view
"""
def get(
self, request, *args, **kwargs
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,unused-argument
"""
Handle get for create view
"""
output_format = "svg+xml"
avatar_style = list(pa.AvatarStyle)[0]
skin_color = list(pa.SkinColor)[0]
hair_color = list(pa.HairColor)[0]
facial_hair_type = list(pa.FacialHairType)[0]
top_type = pa.TopType.SHORT_HAIR_SHORT_FLAT
hat_color = list(pa.Color)[0]
mouth_type = list(pa.MouthType)[0]
eyes_type = list(pa.EyesType)[0]
eyebrow_type = list(pa.EyebrowType)[0]
nose_type = list(pa.NoseType)[0]
accessories_type = list(pa.AccessoriesType)[0]
clothe_type = list(pa.ClotheType)[0]
clothe_color = list(pa.Color)[0]
clothe_graphic_type = list(pa.ClotheGraphicType)[0]
if "avatar_style" in request.GET:
avatar_style = list(pa.AvatarStyle)[int(request.GET["avatar_style"])]
if "skin_color" in request.GET:
skin_color = list(pa.SkinColor)[int(request.GET["skin_color"])]
if "hair_color" in request.GET:
hair_color = list(pa.HairColor)[int(request.GET["hair_color"])]
if "facial_hair_type" in request.GET:
facial_hair_type = list(pa.FacialHairType)[
int(request.GET["facial_hair_type"])
]
if "facial_hair_color" in request.GET:
facial_hair_color = list(pa.HairColor)[
int(request.GET["facial_hair_color"])
]
if "top_type" in request.GET:
top_type = list(pa.TopType)[int(request.GET["top_type"])]
if "hat_color" in request.GET:
hat_color = list(pa.Color)[int(request.GET["hat_color"])]
if "mouth_type" in request.GET:
mouth_type = list(pa.MouthType)[int(request.GET["mouth_type"])]
if "eyes_type" in request.GET:
eyes_type = list(pa.EyesType)[int(request.GET["eyes_type"])]
if "eyebrow_type" in request.GET:
eyebrow_type = list(pa.EyebrowType)[int(request.GET["eyebrow_type"])]
if "nose_type" in request.GET:
nose_type = list(pa.NoseType)[int(request.GET["nose_type"])]
if "accessories_type" in request.GET:
accessories_type = list(pa.AccessoriesType)[
int(request.GET["accessories_type"])
]
if "clothe_type" in request.GET:
clothe_type = list(pa.ClotheType)[int(request.GET["clothe_type"])]
if "clothe_color" in request.GET:
clothe_color = list(pa.Color)[int(request.GET["clothe_color"])]
if "clothe_graphic_type" in request.GET:
clothe_graphic_type = list(pa.ClotheGraphicType)[
int(request.GET["clothe_graphic_type"])
]
if "format" in request.GET:
if request.GET["format"] == "png":
output_format = request.GET["format"]
elif request.GET["format"] in ("svg", "svg+xml"):
output_format = "svg+xml"
else:
print(
"Format: '%s' isn't supported" % request.GET["format"]
) # pylint: disable=consider-using-f-string
avatar = pa.PyAvataaar(
style=avatar_style,
skin_color=skin_color,
hair_color=hair_color,
facial_hair_type=facial_hair_type,
facial_hair_color=facial_hair_color,
top_type=top_type,
hat_color=hat_color,
mouth_type=mouth_type,
eye_type=eyes_type,
eyebrow_type=eyebrow_type,
nose_type=nose_type,
accessories_type=accessories_type,
clothe_type=clothe_type,
clothe_color=clothe_color,
clothe_graphic_type=clothe_graphic_type,
)
if output_format == "png":
return HttpResponse(avatar.render_png(), content_type="image/png")
return HttpResponse(avatar.render_svg(), content_type="image/svg+xml")
@method_decorator(login_required, name="dispatch")
class AvatarItemView(View):
"""
View class responsible for providing access to single avatar items
"""
def get(self, request, *args, **kwargs): # pylint: disable=unused-argument
"""
Handle get for create view
"""
item = request.GET["item"]
env = Environment(
loader=PackageLoader("py_avataaars", "templates"),
trim_blocks=True,
lstrip_blocks=True,
autoescape=True,
keep_trailing_newline=False,
extensions=[],
)
template = env.get_template(item)
def uni(attr): # pylint: disable=unused-argument
return None
rendered_template = template.render(
unique_id=uni,
template_path=pa.PyAvataaar._PyAvataaar__template_path, # pylint: disable=protected-access
template_name=pa.PyAvataaar._PyAvataaar__template_name, # pylint: disable=protected-access
facial_hair_type=pa.FacialHairType.DEFAULT,
hat_color=pa.Color.BLACK,
clothe_color=pa.Color.HEATHER,
hair_color=pa.HairColor.BROWN,
facial_hair_color=pa.HairColor.BROWN,
clothe_graphic_type=pa.ClotheGraphicType.BAT,
accessories_type=pa.AccessoriesType.DEFAULT,
).replace("\n", "")
rendered_template = (
'<?xml version="1.0" encoding="utf-8" ?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="280px" height="280px" viewBox="-6 0 274 280" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
+ rendered_template
+ "</svg>"
) # pylint: disable=line-too-long
return HttpResponse(rendered_template, content_type="image/svg+xml")

View File

@@ -1,243 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap4 %}
{% block title %}{% trans 'Avatar Creator' %}{% endblock title %}
{% block content %}
<style>
.achoose {
float:left;
margin-right:2px;
width:20px;
height:20px;
}
</style>
<script>
var skin_color = 0;
var hair_color = 0;
var facial_hair_type = 0;
var facial_hair_color = 0;
var top_type = -1;
var hat_color = 0;
var mouth_type = 0;
var eyes_type = 0;
var eyebrow_type = 0;
var nose_type = 0;
var accessories_type = 0;
var clothe_type = 0;
var clothe_color = 0;
var clothe_graphic_type = 0;
var avatar_style = 0;
var size = 172;
function update_image() {
var url = "{% url 'avataaar' %}?avatar_style=" + avatar_style + "&skin_color=" + skin_color + "&hair_color=" + hair_color +
"&facial_hair_type=" + facial_hair_type + "&facial_hair_color=" + facial_hair_color +
"&top_type=" + top_type + "&hat_color=" + hat_color + "&mouth_type=" + mouth_type +
"&eyes_type=" + eyes_type + "&eyebrow_type=" + eyebrow_type + "&nose_type=" + nose_type +
"&accessories_type=" + accessories_type + "&clothe_type=" + clothe_type +
"&clothe_color=" + clothe_color + "&clothe_graphic_type=" + clothe_graphic_type;
$("#avatar_image").attr('src', url);
$("#avatar_image").attr('width', size + "px");
}
</script>
<div>
<div id="preview_and_download" style="float:left;width:50%;" data-spy="affix" data-offset-top="0" data-offset-bottom="200">
<h3>{% trans 'Adjust your avatar' %}</h3>
<div id="avatar_image_div">
<img id="avatar_image" width="172px">
<script>update_image();</script>
</div>
<div class="form-group" role="group" style="margin-top:5px;align:center;">
<button type="button" class="btn btn-info" onclick='url=$("#avatar_image").attr("src")+"&format=svg";window.open(url);'>Download SVG</button>
&nbsp;
<button type="button" class="btn btn-info" onclick='url=$("#avatar_image").attr("src")+"&format=png";window.open(url);'>Download PNG</button>
</div>
</div>
<div id="options" style="float:right;">
<!--
<div class="form-group" role="group">
<label for="preview_size" class="form-label">{% trans 'Preview size'%}</label>
<input type="range" class="form-range" id="preview_size" min="16" max="500" step="1" value="172">
<script>
var preview_size = document.getElementById("preview_size");
preview_size.addEventListener("input", function() {
size = this.value;
update_image();
});
</script>
</div>
-->
<div class="form-group" role="group">
<label for="skincolor" class="form-label">{% trans 'Skin color'%}</label>
<div id="skincolor">
{% for color in SkinColor %}
<a class="button achoose" style="background:{{ color.main_value }};" onclick='skin_color="{{ color.value }}"; update_image();'>&nbsp;</a>
{% endfor %}
</div>
</div>
<br/>
<div class="form-group" role="group">
<label for="haircolor" class="form-label">{% trans 'Hair color'%}</label>
<div id="haircolor">
{% for color in HairColor %}
<a class="button achoose" style="background:{{ color.main_value }};" onclick='hair_color="{{ color.value }}"; update_image();'>&nbsp;</a>
{% endfor %}
</div>
</div>
<br/>
<div class="form-group" role="group" style="float:none;">
<label for="facialhairtype" class="form-label">{% trans 'Facial hair type' %}</label>
<input type="range" class="form-range" id="facialhairtype" min="0" max="5" step="1" value="0">
<script>
var elem = document.getElementById("facialhairtype");
facialhairtype.addEventListener("input", function() {
facial_hair_type = this.value;
var thediv = document.getElementById("facialhaircolor").parentNode;
if(this.value != 0) {
thediv.display = "block";
thediv.removeAttribute("hidden");
} else {
thediv.setAttribute("hidden", "hidden");
}
update_image();
});
</script>
</div>
<div hidden="hidden" class="form-group" style="float:none;">
<label for="facialhaircolor" class="form-label">{% trans 'Facial hair color'%}</label>
<div id="facialhaircolor">
{% for color in HairColor %}
<a class="button achoose" style="background:{{ color.main_value }};" onclick='facial_hair_color="{{ color.value }}"; update_image();'>&nbsp;</a>
{% endfor %}
</div>
<br/>
</div>
<div class="form-group" role="group" style="float:none;">
<label for="toptype" class="form-label">{% trans 'Top type' %}</label>
<input type="range" class="form-range" id="toptype" min="0" max="34" step="1" value="0">
<script>
var elem = document.getElementById("toptype");
elem.addEventListener("input", function() {
top_type = this.value;
update_image();
});
</script>
</div>
<div class="form-group" role="group" style="float:none;">
<label for="hatcolor" class="form-label">{% trans 'Hat color'%}</label>
<div id="hatcolor">
{% for color in HatColor %}
<a class="button achoose" style="background:{{ color.main_value }};" onclick='hat_color="{{ color.value }}"; update_image();'>&nbsp;</a>
{% endfor %}
</div>
</div>
<br/>
<div class="form-group" role="group" style="float:none;">
<label for="mouthtype" class="form-label">{% trans 'Mouth type' %}</label>
<input type="range" class="form-range" id="mouthtype" min="0" max="11" step="1" value="0">
<script>
var elem = document.getElementById("mouthtype");
elem.addEventListener("input", function() {
mouth_type = this.value;
update_image();
});
</script>
</div>
<div class="form-group" role="group" style="float:none;">
<label for="eyestype" class="form-label">{% trans 'Eyes type' %}</label>
<input type="range" class="form-range" id="eyestype" min="0" max="11" step="1" value="0">
<script>
var elem = document.getElementById("eyestype");
elem.addEventListener("input", function() {
eyes_type = this.value;
update_image();
});
</script>
</div>
<div class="form-group" role="group" style="float:none;">
<label for="eyebrowtype" class="form-label">{% trans 'Eyebrow type' %}</label>
<input type="range" class="form-range" id="eyebrowtype" min="0" max="12" step="1" value="0">
<script>
var elem = document.getElementById("eyebrowtype");
elem.addEventListener("input", function() {
eyebrow_type = this.value;
update_image();
});
</script>
</div>
<div class="form-group" role="group" style="float:none;">
<label for="accessoriestype" class="form-label">{% trans 'Accessories type' %}</label>
<input type="range" class="form-range" id="accessoriestype" min="0" max="6" step="1" value="0">
<script>
var elem = document.getElementById("accessoriestype");
elem.addEventListener("input", function() {
accessories_type = this.value;
update_image();
});
</script>
</div>
<div class="form-group" role="group" style="float:none;">
<label for="clothetype" class="form-label">{% trans 'Clothe type' %}</label>
<input type="range" class="form-range" id="clothetype" min="0" max="8" step="1" value="0">
<script>
var elem = document.getElementById("clothetype");
elem.addEventListener("input", function() {
clothe_type = this.value;
var thediv = document.getElementById("clothegraphictype").parentNode;
if(this.value == 3) {
thediv.display = "block";
thediv.removeAttribute("hidden");
} else {
thediv.setAttribute("hidden", "hidden");
}
update_image();
});
</script>
</div>
<div class="form-group" role="group" style="float:none;">
<label for="clothegraphictype" class="form-label">{% trans 'Clothe color' %}</label>
<div id"=clothegraphictype">
{% for color in ClotheColor %}
<a class="button achoose" style="background:{{ color.main_value }};" onclick='clothe_color="{{ color.value }}"; update_image();'>&nbsp;</a>
{% endfor %}
</div>
</div>
<br/>
<div hidden="hidden" class="form-group" role="group" style="float:none;">
<label for="clothegraphictype" class="form-label">{% trans 'Clothe graphic type' %}</label>
<input type="range" class="form-range" id="clothegraphictype" min="0" max="10" step="1" value="0">
<script>
var elem = document.getElementById("clothegraphictype");
elem.addEventListener("input", function() {
clothe_graphic_type = this.value;
update_image();
});
</script>
</div>
</div>
</div>
<div>
</div>
<div style="height:40px"></div>
{% endblock content %}

View File

@@ -242,8 +242,7 @@
{% if not max_photos %}
<p>
<a href="{% url 'upload_photo' %}" class="button">{% trans 'Upload a new photo' %}</a>&nbsp;
<a href="{% url 'import_photo' %}" class="button">{% trans 'Import photo from other services' %}</a>&nbsp;
<a href="{% url 'avatar_creator' %}" class="button">{% trans 'Create an avatar' %}</a>
<a href="{% url 'import_photo' %}" class="button">{% trans 'Import photo from other services' %}</a>
</p>
{% else %}
{% trans "You've reached the maximum number of allowed images!" %}<br/>

View File

@@ -26,9 +26,6 @@ from .views import ResendConfirmationMailView
from .views import IvatarLoginView
from .views import DeleteAccountView
from .views import ExportView
from .avatar_creator_views import AvatarCreatorView, AvatarView
# from .avatar_creator_views import AvatarItemView
# Define URL patterns, self documenting
# To see the fancy, colorful evaluation of these use:
@@ -83,10 +80,6 @@ urlpatterns = [ # pylint: disable=invalid-name
path("add_openid/", AddOpenIDView.as_view(), name="add_openid"),
path("upload_photo/", UploadPhotoView.as_view(), name="upload_photo"),
path("password_set/", PasswordSetView.as_view(), name="password_set"),
path("avatar_creator/", AvatarCreatorView.as_view(), name="avatar_creator"),
path("avatar_view/", AvatarView.as_view(), name="avataaar"),
# This is for testing purpose only and shall not be used in production at all
# path("avatar_item_view/", AvatarItemView.as_view(), name="avataaar_item"),
re_path(
r"remove_unconfirmed_openid/(?P<openid_id>\d+)",
RemoveUnconfirmedOpenIDView.as_view(),

View File

@@ -23,7 +23,6 @@ notsetuptools
Pillow
pip
psycopg2-binary
py-avataaars
py3dns
pydocstyle
pyLibravatar