From e88b84938747791a832bd002154ae8031c7ff899 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Tue, 21 Mar 2023 09:41:18 +0100 Subject: [PATCH 01/23] Add numerise cli function, tweak hashtag display --- cli.py | 22 +++++++++ dribdat/admin/forms.py | 4 +- dribdat/public/forms.py | 2 +- dribdat/static/css/style.css | 13 ++++-- poetry.lock | 90 +++++++++++++++++------------------- publish-docker.sh | 3 ++ requirements/prod.txt | 6 +-- 7 files changed, 82 insertions(+), 58 deletions(-) create mode 100755 publish-docker.sh diff --git a/cli.py b/cli.py index 90c2b363..6ab0454d 100755 --- a/cli.py +++ b/cli.py @@ -33,6 +33,27 @@ def socialize(kind): print("Updated %d users." % len(q)) +@click.command() +@click.argument('event', required=True) +def numerise(event: int): + """Assign numbers to challenges.""" + # TODO: use a parameter for alphabetic, id-based, etc. + # Generate some primes, thanks @DhanushNayak + primes = list(filter(lambda x: not list(filter(lambda y : x%y==0, range(2,x))),range(2,200))) + with create_app().app_context(): + from dribdat.user.models import Event + challenges = Event.query.filter_by(id=event).first_or_404().projects + ix = 0 + for c in challenges: + ch = "" # push existing hashtag aside + if len(c.hashtag) > 0: + ch = " " + ch + c.hashtag = str(primes[ix]) + ch + c.save() + ix = ix + 1 + print("Enumerated %d challenges." % len(challenges)) + + @click.command() @click.argument('name', required=True) @click.argument('start', required=False) @@ -104,6 +125,7 @@ def cli(): cli.add_command(socialize) +cli.add_command(numerise) cli.add_command(imports) cli.add_command(exports) diff --git a/dribdat/admin/forms.py b/dribdat/admin/forms.py index d999d7d0..0aab2232 100644 --- a/dribdat/admin/forms.py +++ b/dribdat/admin/forms.py @@ -176,7 +176,7 @@ class ProjectForm(FlaskForm): source_url = URLField(u'Source link', [length(max=2048)]) download_url = URLField(u'Download link', [length(max=2048)]) image_url = URLField(u'Image link', [length(max=255)]) - logo_color = StringField(u'Custom color', [length(max=7)]) + logo_color = StringField(u'Custom color') logo_icon = StringField(u'Custom icon', [length(max=20)]) submit = SubmitField(u'Save') @@ -188,7 +188,7 @@ class CategoryForm(FlaskForm): name = StringField(u'Name', [length(max=80), DataRequired()]) description = TextAreaField(u'Description', description=u'Markdown and HTML supported') - logo_color = StringField(u'Custom color', [length(max=7)]) + logo_color = StringField(u'Custom color') logo_icon = StringField(u'Custom icon', [length(max=20)], description=u'fontawesome.com/v4/cheatsheet') event_id = SelectField(u'Specific to an event, or global if blank', diff --git a/dribdat/public/forms.py b/dribdat/public/forms.py index 1e11e294..699648f1 100644 --- a/dribdat/public/forms.py +++ b/dribdat/public/forms.py @@ -99,7 +99,7 @@ class ProjectDetailForm(FlaskForm): u'Cover image', [length(max=255)], description="URL of an image to display at the top of the page.") logo_color = StringField( - u'Color scheme', [length(max=7)], + u'Color scheme', description="Customize the color of your project page.") logo_icon = StringField( u'Named icon', diff --git a/dribdat/static/css/style.css b/dribdat/static/css/style.css index 14639ac7..b4eadfb7 100644 --- a/dribdat/static/css/style.css +++ b/dribdat/static/css/style.css @@ -752,13 +752,18 @@ nav .nav-login { hyphens: auto; } .tooltip .tooltip-inner span { /* Hashtag */ - font-weight: bold; - font-size: 80%; - font-family: monospace; overflow: hidden; max-height: 1.5em; display: block; } +.tooltip .tooltip-inner span, +.project-page .project-hashtag { + font-weight: bold; + font-size: 150%; + color: red; + text-shadow: 1px 1px 1px white; + font-family: monospace; +} .tooltip .tooltip-inner img { max-width: 100%; max-height: 13em; @@ -1518,7 +1523,7 @@ a.go-up { .widget-team .kick-user { position: absolute; font-size: 150%; - margin-left: -2.2em; + margin-top: -1em; } .widget-team .kick-user:hover { font-weight: bold; diff --git a/poetry.lock b/poetry.lock index f9ccc3dc..aa71862e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -68,7 +68,7 @@ typecheck = ["mypy"] [[package]] name = "beautifulsoup4" -version = "4.11.2" +version = "4.12.0" description = "Screen-scraping library" category = "dev" optional = false @@ -323,7 +323,7 @@ doc = ["sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "17.6.0" +version = "18.2.0" description = "Faker is a Python package that generates fake data for you." category = "dev" optional = false @@ -1232,14 +1232,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "redis" -version = "4.5.1" +version = "4.5.2" description = "Python client for Redis database and key-value store" category = "main" optional = false python-versions = ">=3.7" [package.dependencies] -async-timeout = ">=4.0.2" +async-timeout = {version = ">=4.0.2", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""} typing-extensions = {version = "*", markers = "python_version < \"3.8\""} @@ -1656,11 +1656,11 @@ test = ["zope.testrunner"] [[package]] name = "zope.interface" -version = "5.5.2" +version = "6.0" description = "Interfaces for Python" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" [package.extras] docs = ["sphinx", "repoze.sphinx.autointerface"] @@ -1688,8 +1688,8 @@ async-timeout = [ attrs = [] bcrypt = [] beautifulsoup4 = [ - {file = "beautifulsoup4-4.11.2-py3-none-any.whl", hash = "sha256:0e79446b10b3ecb499c1556f7e228a53e64a2bfcebd455f370d8927cb5b59e39"}, - {file = "beautifulsoup4-4.11.2.tar.gz", hash = "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106"}, + {file = "beautifulsoup4-4.12.0-py3-none-any.whl", hash = "sha256:2130a5ad7f513200fae61a17abb5e338ca980fa28c439c0571014bc0217e9591"}, + {file = "beautifulsoup4-4.12.0.tar.gz", hash = "sha256:c5fceeaec29d09c84970e47c65f2f0efe57872f7cff494c9691a26ec0ff13234"}, ] bleach = [ {file = "bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"}, @@ -1895,8 +1895,8 @@ factory-boy = [ {file = "factory_boy-3.2.1.tar.gz", hash = "sha256:a98d277b0c047c75eb6e4ab8508a7f81fb03d2cb21986f627913546ef7a2a55e"}, ] faker = [ - {file = "Faker-17.6.0-py3-none-any.whl", hash = "sha256:5aaa16fa9cfde7d117eef70b6b293a705021e57158f3fa6b44ed1b70202d2065"}, - {file = "Faker-17.6.0.tar.gz", hash = "sha256:51f37ff9df710159d6d736d0ba1c75e063430a8c806b91334d7794305b5a6114"}, + {file = "Faker-18.2.0-py3-none-any.whl", hash = "sha256:20272ff42eaa3fd0a83d1c4ad27a6c0a58ebded4e48411a635b31643087d4bd6"}, + {file = "Faker-18.2.0.tar.gz", hash = "sha256:daf4fc907cdc009bfde011db845d9847e0293baa701c49607bac1184f11e5d95"}, ] flake8 = [] flake8-blind-except = [ @@ -2383,8 +2383,8 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] redis = [ - {file = "redis-4.5.1-py3-none-any.whl", hash = "sha256:5deb072d26e67d2be1712603bfb7947ec3431fb0eec9c578994052e33035af6d"}, - {file = "redis-4.5.1.tar.gz", hash = "sha256:1eec3741cda408d3a5f84b78d089c8b8d895f21b3b050988351e925faf202864"}, + {file = "redis-4.5.2-py3-none-any.whl", hash = "sha256:4b12b3a1e9bfb43dc533330ec6d142329d0c27ea6bb6716a9d0389e8f2038a4e"}, + {file = "redis-4.5.2.tar.gz", hash = "sha256:d8ae1a4f725eea6e3958411870fdf944c587b00ea12be28d3bd5575d8f26430c"}, ] requests = [ {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, @@ -2531,40 +2531,34 @@ zipp = [ {file = "zope.event-4.6.tar.gz", hash = "sha256:81d98813046fc86cc4136e3698fee628a3282f9c320db18658c21749235fce80"}, ] "zope.interface" = [ - {file = "zope.interface-5.5.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:a2ad597c8c9e038a5912ac3cf166f82926feff2f6e0dabdab956768de0a258f5"}, - {file = "zope.interface-5.5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:65c3c06afee96c654e590e046c4a24559e65b0a87dbff256cd4bd6f77e1a33f9"}, - {file = "zope.interface-5.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d514c269d1f9f5cd05ddfed15298d6c418129f3f064765295659798349c43e6f"}, - {file = "zope.interface-5.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5334e2ef60d3d9439c08baedaf8b84dc9bb9522d0dacbc10572ef5609ef8db6d"}, - {file = "zope.interface-5.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc26c8d44472e035d59d6f1177eb712888447f5799743da9c398b0339ed90b1b"}, - {file = "zope.interface-5.5.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17ebf6e0b1d07ed009738016abf0d0a0f80388e009d0ac6e0ead26fc162b3b9c"}, - {file = "zope.interface-5.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f98d4bd7bbb15ca701d19b93263cc5edfd480c3475d163f137385f49e5b3a3a7"}, - {file = "zope.interface-5.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:696f3d5493eae7359887da55c2afa05acc3db5fc625c49529e84bd9992313296"}, - {file = "zope.interface-5.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7579960be23d1fddecb53898035a0d112ac858c3554018ce615cefc03024e46d"}, - {file = "zope.interface-5.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:765d703096ca47aa5d93044bf701b00bbce4d903a95b41fff7c3796e747b1f1d"}, - {file = "zope.interface-5.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e945de62917acbf853ab968d8916290548df18dd62c739d862f359ecd25842a6"}, - {file = "zope.interface-5.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:655796a906fa3ca67273011c9805c1e1baa047781fca80feeb710328cdbed87f"}, - {file = "zope.interface-5.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:0fb497c6b088818e3395e302e426850f8236d8d9f4ef5b2836feae812a8f699c"}, - {file = "zope.interface-5.5.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:008b0b65c05993bb08912f644d140530e775cf1c62a072bf9340c2249e613c32"}, - {file = "zope.interface-5.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:404d1e284eda9e233c90128697c71acffd55e183d70628aa0bbb0e7a3084ed8b"}, - {file = "zope.interface-5.5.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3218ab1a7748327e08ef83cca63eea7cf20ea7e2ebcb2522072896e5e2fceedf"}, - {file = "zope.interface-5.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d169ccd0756c15bbb2f1acc012f5aab279dffc334d733ca0d9362c5beaebe88e"}, - {file = "zope.interface-5.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e1574980b48c8c74f83578d1e77e701f8439a5d93f36a5a0af31337467c08fcf"}, - {file = "zope.interface-5.5.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:0217a9615531c83aeedb12e126611b1b1a3175013bbafe57c702ce40000eb9a0"}, - {file = "zope.interface-5.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:311196634bb9333aa06f00fc94f59d3a9fddd2305c2c425d86e406ddc6f2260d"}, - {file = "zope.interface-5.5.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6373d7eb813a143cb7795d3e42bd8ed857c82a90571567e681e1b3841a390d16"}, - {file = "zope.interface-5.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:959697ef2757406bff71467a09d940ca364e724c534efbf3786e86eee8591452"}, - {file = "zope.interface-5.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dbaeb9cf0ea0b3bc4b36fae54a016933d64c6d52a94810a63c00f440ecb37dd7"}, - {file = "zope.interface-5.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604cdba8f1983d0ab78edc29aa71c8df0ada06fb147cea436dc37093a0100a4e"}, - {file = "zope.interface-5.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e74a578172525c20d7223eac5f8ad187f10940dac06e40113d62f14f3adb1e8f"}, - {file = "zope.interface-5.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0980d44b8aded808bec5059018d64692f0127f10510eca71f2f0ace8fb11188"}, - {file = "zope.interface-5.5.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6e972493cdfe4ad0411fd9abfab7d4d800a7317a93928217f1a5de2bb0f0d87a"}, - {file = "zope.interface-5.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9d783213fab61832dbb10d385a319cb0e45451088abd45f95b5bb88ed0acca1a"}, - {file = "zope.interface-5.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:a16025df73d24795a0bde05504911d306307c24a64187752685ff6ea23897cb0"}, - {file = "zope.interface-5.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:40f4065745e2c2fa0dff0e7ccd7c166a8ac9748974f960cd39f63d2c19f9231f"}, - {file = "zope.interface-5.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a2ffadefd0e7206adc86e492ccc60395f7edb5680adedf17a7ee4205c530df4"}, - {file = "zope.interface-5.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d692374b578360d36568dd05efb8a5a67ab6d1878c29c582e37ddba80e66c396"}, - {file = "zope.interface-5.5.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4087e253bd3bbbc3e615ecd0b6dd03c4e6a1e46d152d3be6d2ad08fbad742dcc"}, - {file = "zope.interface-5.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fb68d212efd057596dee9e6582daded9f8ef776538afdf5feceb3059df2d2e7b"}, - {file = "zope.interface-5.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:7e66f60b0067a10dd289b29dceabd3d0e6d68be1504fc9d0bc209cf07f56d189"}, - {file = "zope.interface-5.5.2.tar.gz", hash = "sha256:bfee1f3ff62143819499e348f5b8a7f3aa0259f9aca5e0ddae7391d059dce671"}, + {file = "zope.interface-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f299c020c6679cb389814a3b81200fe55d428012c5e76da7e722491f5d205990"}, + {file = "zope.interface-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee4b43f35f5dc15e1fec55ccb53c130adb1d11e8ad8263d68b1284b66a04190d"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a158846d0fca0a908c1afb281ddba88744d403f2550dc34405c3691769cdd85"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72f23bab1848edb7472309e9898603141644faec9fd57a823ea6b4d1c4c8995"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f4d38cf4b462e75fac78b6f11ad47b06b1c568eb59896db5b6ec1094eb467f"}, + {file = "zope.interface-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:87b690bbee9876163210fd3f500ee59f5803e4a6607d1b1238833b8885ebd410"}, + {file = "zope.interface-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2363e5fd81afb650085c6686f2ee3706975c54f331b426800b53531191fdf28"}, + {file = "zope.interface-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af169ba897692e9cd984a81cb0f02e46dacdc07d6cf9fd5c91e81f8efaf93d52"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa90bac61c9dc3e1a563e5babb3fd2c0c1c80567e815442ddbe561eadc803b30"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89086c9d3490a0f265a3c4b794037a84541ff5ffa28bb9c24cc9f66566968464"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:809fe3bf1a91393abc7e92d607976bbb8586512913a79f2bf7d7ec15bd8ea518"}, + {file = "zope.interface-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:0ec9653825f837fbddc4e4b603d90269b501486c11800d7c761eee7ce46d1bbb"}, + {file = "zope.interface-6.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:790c1d9d8f9c92819c31ea660cd43c3d5451df1df61e2e814a6f99cebb292788"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39b8711578dcfd45fc0140993403b8a81e879ec25d53189f3faa1f006087dca"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eba51599370c87088d8882ab74f637de0c4f04a6d08a312dce49368ba9ed5c2a"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee934f023f875ec2cfd2b05a937bd817efcc6c4c3f55c5778cbf78e58362ddc"}, + {file = "zope.interface-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:042f2381118b093714081fd82c98e3b189b68db38ee7d35b63c327c470ef8373"}, + {file = "zope.interface-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dfbbbf0809a3606046a41f8561c3eada9db811be94138f42d9135a5c47e75f6f"}, + {file = "zope.interface-6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:424d23b97fa1542d7be882eae0c0fc3d6827784105264a8169a26ce16db260d8"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e538f2d4a6ffb6edfb303ce70ae7e88629ac6e5581870e66c306d9ad7b564a58"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12175ca6b4db7621aedd7c30aa7cfa0a2d65ea3a0105393e05482d7a2d367446"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3d7dfd897a588ec27e391edbe3dd320a03684457470415870254e714126b1f"}, + {file = "zope.interface-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b3f543ae9d3408549a9900720f18c0194ac0fe810cecda2a584fd4dca2eb3bb8"}, + {file = "zope.interface-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0583b75f2e70ec93f100931660328965bb9ff65ae54695fb3fa0a1255daa6f2"}, + {file = "zope.interface-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:23ac41d52fd15dd8be77e3257bc51bbb82469cf7f5e9a30b75e903e21439d16c"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99856d6c98a326abbcc2363827e16bd6044f70f2ef42f453c0bd5440c4ce24e5"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1592f68ae11e557b9ff2bc96ac8fc30b187e77c45a3c9cd876e3368c53dc5ba8"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4407b1435572e3e1610797c9203ad2753666c62883b921318c5403fb7139dec2"}, + {file = "zope.interface-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:5171eb073474a5038321409a630904fd61f12dd1856dd7e9d19cd6fe092cbbc5"}, + {file = "zope.interface-6.0.tar.gz", hash = "sha256:aab584725afd10c710b8f1e6e208dbee2d0ad009f57d674cb9d1b3964037275d"}, ] diff --git a/publish-docker.sh b/publish-docker.sh new file mode 100755 index 00000000..5cb42d12 --- /dev/null +++ b/publish-docker.sh @@ -0,0 +1,3 @@ +docker login +docker build -t loleg/dribdat:stable . +docker push loleg/dribdat:stable diff --git a/requirements/prod.txt b/requirements/prod.txt index ed0d5ec8..3ce2844b 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1,7 +1,7 @@ -i https://pypi.org/simple alembic==1.10.2; python_version >= "3.7" aniso8601==9.0.1 -async-timeout==4.0.2; python_version >= "3.7" +async-timeout==4.0.2; python_version < "3.11" and python_version >= "3.7" attrs==22.2.0; python_version >= "3.7" bcrypt==4.0.1; python_version >= "3.6" bleach==6.0.0; python_version >= "3.7" @@ -73,7 +73,7 @@ python-dotenv==0.21.1; python_version >= "3.7" python-slugify==8.0.1; python_version >= "3.7" pytz==2022.7.1 pyyaml==5.4.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") -redis==4.5.1; python_version >= "3.7" +redis==4.5.2; python_version >= "3.7" requests-oauthlib==1.3.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" requests==2.28.2; python_version >= "3.7" and python_version < "4" rfc3986==2.0.0; python_version >= "3.7" @@ -100,4 +100,4 @@ whitenoise==5.3.0; python_version >= "3.5" and python_version < "4" wtforms==3.0.1; python_version >= "3.7" zipp==3.15.0; python_version < "3.8" and python_version >= "3.7" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.6.0" and python_version < "3.8" and python_version >= "3.6") zope.event==4.6; python_version >= "3.5" and python_full_version < "3.0.0" or python_version > "3.5" -zope.interface==5.5.2; python_version >= "3.5" and python_full_version < "3.0.0" or python_version > "3.5" and python_full_version < "3.0.0" or python_version > "3.5" and python_full_version >= "3.5.0" +zope.interface==6.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" From 59fac841e05bfa3bf105701400c9225caca17b0e Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Tue, 21 Mar 2023 11:08:02 +0100 Subject: [PATCH 02/23] Update numerise, cache API --- cli.py | 11 +++++++---- dribdat/public/api.py | 5 +++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cli.py b/cli.py index 6ab0454d..e9a370d1 100755 --- a/cli.py +++ b/cli.py @@ -35,18 +35,21 @@ def socialize(kind): @click.command() @click.argument('event', required=True) -def numerise(event: int): - """Assign numbers to challenges.""" +@click.argument('clear', required=False, default=False) +def numerise(event: int, clear: bool): + """Assign numbers to challenge hashtags for an EVENT ID.""" # TODO: use a parameter for alphabetic, id-based, etc. # Generate some primes, thanks @DhanushNayak primes = list(filter(lambda x: not list(filter(lambda y : x%y==0, range(2,x))),range(2,200))) with create_app().app_context(): from dribdat.user.models import Event - challenges = Event.query.filter_by(id=event).first_or_404().projects + challenges = Event.query.filter_by(id=event) \ + .first_or_404().projects ix = 0 for c in challenges: + if c.is_hidden: continue ch = "" # push existing hashtag aside - if len(c.hashtag) > 0: + if not clear and len(c.hashtag) > 0: ch = " " + ch c.hashtag = str(primes[ix]) + ch c.save() diff --git a/dribdat/public/api.py b/dribdat/public/api.py index 146e9c5d..85013304 100644 --- a/dribdat/public/api.py +++ b/dribdat/public/api.py @@ -13,7 +13,7 @@ from flask_login import login_required, current_user from sqlalchemy import or_ -from ..extensions import db +from ..extensions import db, cache from ..utils import timesince, random_password from ..decorators import admin_required from ..user.models import Event, Project, Activity @@ -495,8 +495,8 @@ def generate_event_package(event, format='json'): package.to_zip(fp_package.name) return send_file(fp_package.name, as_attachment=True) - @blueprint.route('/event/current/datapackage.', methods=["GET"]) +@cache.cached() def package_current_event(format): """Download a Data Package for an event.""" event = Event.query.filter_by(is_current=True).first() or \ @@ -505,6 +505,7 @@ def package_current_event(format): @blueprint.route('/event//datapackage.', methods=["GET"]) +@cache.cached() def package_specific_event(event_id, format): """Download a Data Package for an event.""" event = Event.query.filter_by(id=event_id).first_or_404() From 541d4814936d231bed8c7ecc1a49dc0face28fa5 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Tue, 21 Mar 2023 18:19:30 +0100 Subject: [PATCH 03/23] Event social share and print view --- cli.py | 8 ++++---- dribdat/public/views.py | 4 ++-- dribdat/static/css/style.css | 13 ++++++------- dribdat/static/js/editor.js | 7 ++++++- dribdat/templates/public/event.html | 2 +- dribdat/templates/public/eventprint.html | 11 ++++++++++- dribdat/templates/public/login.html | 8 ++++++++ dribdat/templates/public/projectedit.html | 4 ++-- 8 files changed, 39 insertions(+), 18 deletions(-) diff --git a/cli.py b/cli.py index e9a370d1..2933ac9e 100755 --- a/cli.py +++ b/cli.py @@ -43,10 +43,10 @@ def numerise(event: int, clear: bool): primes = list(filter(lambda x: not list(filter(lambda y : x%y==0, range(2,x))),range(2,200))) with create_app().app_context(): from dribdat.user.models import Event - challenges = Event.query.filter_by(id=event) \ - .first_or_404().projects + projects = Event.query.filter_by(id=event) \ + .first_or_404().projects ix = 0 - for c in challenges: + for c in projects: if c.is_hidden: continue ch = "" # push existing hashtag aside if not clear and len(c.hashtag) > 0: @@ -54,7 +54,7 @@ def numerise(event: int, clear: bool): c.hashtag = str(primes[ix]) + ch c.save() ix = ix + 1 - print("Enumerated %d challenges." % len(challenges)) + print("Enumerated %d projects." % len(projects)) @click.command() diff --git a/dribdat/public/views.py b/dribdat/public/views.py index 466ba6fa..d0449e4e 100644 --- a/dribdat/public/views.py +++ b/dribdat/public/views.py @@ -256,8 +256,8 @@ def event_print(event_id): now = datetime.utcnow().strftime("%d.%m.%Y %H:%M") event = Event.query.filter_by(id=event_id).first_or_404() eventdata = Project.query.filter_by(event_id=event_id, is_hidden=False) - projects = eventdata.filter(Project.progress >= 0).order_by(Project.name) - challenges = eventdata.filter(Project.progress < 0).order_by(Project.name) + projects = eventdata.filter(Project.progress > 0).order_by(Project.name) + challenges = eventdata.filter(Project.progress <= 0).order_by(Project.id) return render_template('public/eventprint.html', active='print', projects=projects, challenges=challenges, current_event=event, curdate=now) diff --git a/dribdat/static/css/style.css b/dribdat/static/css/style.css index b4eadfb7..afbc7986 100644 --- a/dribdat/static/css/style.css +++ b/dribdat/static/css/style.css @@ -757,7 +757,7 @@ nav .nav-login { display: block; } .tooltip .tooltip-inner span, -.project-page .project-hashtag { +.project-hashtag { font-weight: bold; font-size: 150%; color: red; @@ -1982,43 +1982,42 @@ SlackIn button -------------- */ .signin-slack { - font-size: 0px; border: 0px !important; background: url('/static/img/sso/slack-sign-in.png') no-repeat; width: 173px; height: 41px; } .signin-azure { border: 0px !important; - padding-left: 50px; + padding-left: 50px; font-size: initial !important; background: url('/static/img/sso/azure_teams.svg') no-repeat; width: auto; height: 41px; } .signin-github { - font-size: 0px; border: 0px !important; + border: 0px !important; background: url('/static/img/sso/github-sign-in.png') no-repeat; width: 372px; height: 64px; } .signin-auth0 { - font-size: 0px; border: 1px solid #999; background: url('/static/img/sso/auth0_icon.png') no-repeat; width: 156px; height: 71px; } .signin-mattermost { border: 1px solid #999; - font-size: 0px; background: url('/static/img/sso/Mattermost-Logo-Denim.svg') no-repeat; width: 190px; height: 43px; } .signin-hitobito { border: 1px solid #999; - font-size: 0px; background: url('/static/img/sso/hitobito.png') no-repeat; width: 260px; height: 58px; } .sso-login .btn:hover { border: 1px solid blue; } +.sso-login .btn { + font-size: 0px; +} .account-register a { margin-left: 3.5em; font-size: 125%; } .account-register::before, .sso-login::after { diff --git a/dribdat/static/js/editor.js b/dribdat/static/js/editor.js index ae9775d2..07ab5f69 100644 --- a/dribdat/static/js/editor.js +++ b/dribdat/static/js/editor.js @@ -250,8 +250,13 @@ $('#is_webembed:not(:checked)').click(); $dialog.modal('hide'); } else if ($(this).data('target') == 'pitch') { + // Determine file extension + //var fileExt = filename.split('.'); + //fileExt = (fileExt.length > 1) ? fileExt[fileExt.length - 1] : '?'; + // ... ' (' + fileExt.toUpperCase() + ')'; + // Create Markdown link with a paperclip emoji + var fileLink = '📎 [' + filename + '](' + response + ')'; // Append to pitch - var fileLink = '📦 File: [' + filename + '](' + response + ')'; if (typeof window.toasteditor !== 'undefined') { window.toasteditor.insertText(fileLink); } else { diff --git a/dribdat/templates/public/event.html b/dribdat/templates/public/event.html index ddfd075c..e030ca37 100644 --- a/dribdat/templates/public/event.html +++ b/dribdat/templates/public/event.html @@ -196,7 +196,7 @@

{{ category.name }}

{% endif %} {% if config.DRIBDAT_SOCIAL_LINKS %} - {{ social.share_links(current_event.hashtag, url_for('public.event', event_id=current_event.id, _external=True)) }} + {{ social.share_links(current_event.hashtags, url_for('public.event', event_id=current_event.id, _external=True)) }} {% endif %} {% endblock %} diff --git a/dribdat/templates/public/eventprint.html b/dribdat/templates/public/eventprint.html index a8ae97ff..4d923837 100644 --- a/dribdat/templates/public/eventprint.html +++ b/dribdat/templates/public/eventprint.html @@ -27,7 +27,7 @@
+ + {% if project.hashtag %} +

{{project.hashtag}}

+ {% endif %} +

{% if project.logo_icon %} diff --git a/dribdat/templates/public/login.html b/dribdat/templates/public/login.html index d43d82ba..ba97c230 100644 --- a/dribdat/templates/public/login.html +++ b/dribdat/templates/public/login.html @@ -7,6 +7,13 @@

Log in


+ + {% if oauth_type %} +

+ Use the button below to sign in with your {{ oauth_type|capitalize }} account. +

+ {% endif %} + {% if oauth_type == 'slack' %}

{% endif %} +
diff --git a/dribdat/templates/macros/_event.html b/dribdat/templates/macros/_event.html index ad16026c..d99d8424 100644 --- a/dribdat/templates/macros/_event.html +++ b/dribdat/templates/macros/_event.html @@ -29,12 +29,10 @@
- {% if project.is_challenge and project.team %} - {{ project.team|count }} - {% elif project.score and project.score > 80 %} - 🚀 + {% if project.score and project.score > 90 %} + 🚀 {% elif project.score and project.score > 69 %} - 🔥 + 🔥 {% endif %} {{ project.name|truncate(64, True, '..') }} {% if project.image_url %} @@ -43,11 +41,22 @@
{% endif %}
- {% if project.score and project.score > 0 %} -
-
+ {% if project.team_count > 0 %} +
+ {% if project.team_count == 1 %} + + {% elif project.team_count == 2 %} + + {% elif project.team_count > 2 %} + + {% endif %} +
+ {% elif project.score and project.score > 0 %} +
+
+
-
{% endif %} {% endmacro %} @@ -131,7 +140,6 @@
{{ project.name }}
{% if not event.lock_resources %} -

{{event.name}} @@ -155,7 +163,7 @@

{% if event.lock_resources %} - + {{event.name}} {% elif event.has_finished %} @@ -165,7 +173,7 @@

{% else %} - + Challenges {% endif %} diff --git a/dribdat/templates/public/event.html b/dribdat/templates/public/event.html index e030ca37..74bdd1d9 100644 --- a/dribdat/templates/public/event.html +++ b/dribdat/templates/public/event.html @@ -91,7 +91,7 @@

{{ category.name }}

{% if current_event.can_start_project %} {% if current_event.lock_resources %} - + Share a resource {% elif current_event.has_started %} diff --git a/dribdat/templates/public/project.html b/dribdat/templates/public/project.html index 9494b9f5..ac8bc03f 100644 --- a/dribdat/templates/public/project.html +++ b/dribdat/templates/public/project.html @@ -138,7 +138,7 @@
{{ project.name }}
{% if project.score and project.score > 1 %}
- {{ project_dribs|length }} @@ -168,7 +168,7 @@
{{ project.name }}
{% if project.event.lock_resources %}
- + Resource {% endif %} @@ -213,12 +213,22 @@

{{project.name}}

{% endif %} {% if project.autotext %} {% endif %} {% if project_team %} {% endif %} {% if project_dribs %} diff --git a/dribdat/user/models.py b/dribdat/user/models.py index 835b4791..a36ffa94 100644 --- a/dribdat/user/models.py +++ b/dribdat/user/models.py @@ -616,9 +616,10 @@ def data(self): 'id': self.id, 'url': self.url, 'name': self.name, - 'team': self.team, 'score': self.score, 'phase': self.phase, + 'team': self.team, + 'team_count': self.team_count, 'is_challenge': self.is_challenge, 'is_webembed': self.is_webembed, 'progress': self.progress, @@ -668,13 +669,22 @@ def latest_activity(self, max=5): q = q.order_by(Activity.timestamp.desc()) return q.limit(max) + @property + def team_count(self): + """Return follower count.""" + return Activity.query \ + .filter_by(project_id=self.id, name='star') \ + .count() + def get_team(self, with_spectators=False): """Return all starring users (A team).""" - q = Activity.query.filter_by(project_id=self.id) + activities = self.activities if with_spectators: - activities = q.all() + activities = self.activities else: - activities = q.filter_by(name='star').all() + activities = Activity.query \ + .filter_by(project_id=self.id, name='star') \ + .all() members = [] for a in activities: if a.user and a.user not in members: From 7448ccaea86c2fdb78cb1a1a73e87435774f4055 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Tue, 21 Mar 2023 22:38:16 +0100 Subject: [PATCH 05/23] Optimize project stats --- dribdat/apipackage.py | 4 ++-- dribdat/user/models.py | 52 +++++++++++++++++++++++------------------- tests/test_features.py | 4 ++-- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/dribdat/apipackage.py b/dribdat/apipackage.py index 02f8c170..c86f7f28 100644 --- a/dribdat/apipackage.py +++ b/dribdat/apipackage.py @@ -26,7 +26,7 @@ def event_to_data_package(event, author=None, host_url='', full_content=False): """Create a Data Package from the data of an event.""" # Define the author, if available contributors = [] - if author and not author.is_anonymous: + if author and not author.is_anonymous and author.is_admin: contributors.append({ "title": author.username, "path": author.webpage_url or '', @@ -77,7 +77,7 @@ def event_to_data_package(event, author=None, host_url='', full_content=False): name='activities', data=get_event_activities(event.id, 500), )) - # print("Generating in-memory JSON of activities") + # print("Generating in-memory JSON of categories") package.add_resource(Resource( name='categories', data=get_event_categories(event.id), diff --git a/dribdat/user/models.py b/dribdat/user/models.py index a36ffa94..2c3817de 100644 --- a/dribdat/user/models.py +++ b/dribdat/user/models.py @@ -693,39 +693,43 @@ def get_team(self, with_spectators=False): def get_stats(self): """Collect some activity stats.""" - q = Activity.query.filter_by(project_id=self.id) - s_total = q.count() - s_updates = q.filter_by( - name='update' - ).count() - s_commits = q.filter_by( - name='update', action='commit' - ).count() - if self.event: - s_during = q.filter( - Activity.timestamp > self.event.starts_at_tz, - Activity.timestamp < self.event.ends_at_tz - ).count() - else: - s_during = 0 - s_people = len(self.get_team(True)) - # TODO: real wordcount - s_words = 0 + q = self.activities + + # Basic statistics + s_total = len(q) + s_updates = 0 + s_commits = 0 + s_during = 0 + s_people = 0 + for act in q: + if act.name == 'update': + s_updates += 1 + if act.action == 'commit': + s_commits += 1 + elif act.name == 'star': + s_people += 1 + if self.event: + if act.timestamp > self.event.starts_at and \ + act.timestamp < self.event.ends_at: + s_during += 1 + + # A byte count of contents + s_sizepitch = 0 if self.longtext: - s_words = len(self.longtext.split(' ')) - s_allwords = s_words + s_sizepitch += len(self.longtext.strip()) + s_sizetotal = s_sizepitch if self.autotext: - s_allwords += len(self.autotext.split(' ')) + s_sizetotal += len(self.autotext) if self.summary: - s_allwords += len(self.summary.split(' ')) + s_sizetotal += len(self.summary.strip()) return { 'total': s_total, 'updates': s_updates, 'commits': s_commits, 'during': s_during, 'people': s_people, - 'wordslong': s_words, - 'wordcount': s_allwords, + 'sizepitch': s_sizepitch, + 'sizetotal': s_sizetotal, } def get_missing_roles(self): diff --git a/tests/test_features.py b/tests/test_features.py index 53d78575..1a626e06 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -43,8 +43,8 @@ def test_project_api(self, project, testapp): assert stats['commits'] == 0 assert stats['during'] == 0 assert stats['people'] == 0 - assert stats['wordslong'] == 1 - assert stats['wordcount'] == 9 + assert stats['sizepitch'] == 5 + assert stats['sizetotal'] == 48 def test_project_stage(self, project, testapp): """Check stage progression.""" From 4ffb0756869c95804b9fd87389f5324769c77efd Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Tue, 21 Mar 2023 22:57:30 +0100 Subject: [PATCH 06/23] User admin e-mail search and sorted pagination --- dribdat/admin/views.py | 8 ++++++-- dribdat/static/css/style.css | 3 ++- dribdat/templates/admin/events.html | 8 ++++---- dribdat/templates/includes/pagination.html | 6 +++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/dribdat/admin/views.py b/dribdat/admin/views.py index e445aa1d..932bbba0 100644 --- a/dribdat/admin/views.py +++ b/dribdat/admin/views.py @@ -91,15 +91,19 @@ def users(page=1): User.username.asc() ) else: # Default: updated + sort_by = 'updated' users = User.query.order_by( User.updated_at.desc() ) search_by = request.args.get('search') if search_by and len(search_by) > 1: q = "%%%s%%" % search_by.lower() - users = users.filter(User.username.ilike(q)) + if '@' in search_by: + users = users.filter(User.email.ilike(q)) + else: + users = users.filter(User.username.ilike(q)) users = users.paginate(page, per_page=20) - return render_template('admin/users.html', + return render_template('admin/users.html', sort_by=sort_by, data=users, endpoint='admin.users', active='users') diff --git a/dribdat/static/css/style.css b/dribdat/static/css/style.css index 4d78bf52..7b1be554 100644 --- a/dribdat/static/css/style.css +++ b/dribdat/static/css/style.css @@ -599,7 +599,8 @@ nav .nav-login { } .team-counter { font-size: 13pt; - color: rgba(0,0,200,0.4); + color: whitesmoke; + text-shadow: 1px 1px 1px rgba(0,0,200,0.7); position: absolute; text-align: right; width: 94%; diff --git a/dribdat/templates/admin/events.html b/dribdat/templates/admin/events.html index 6308506e..78f66222 100644 --- a/dribdat/templates/admin/events.html +++ b/dribdat/templates/admin/events.html @@ -62,16 +62,16 @@

Events

{% if event.is_current %} - Current + Current {% endif %} {% if event.lock_editing %} - Freeze + Freeze {% endif %} {% if event.lock_starting %} - Lock + Lock {% endif %} {% if event.lock_resources %} - Resource + Resource {% endif %} {% if event.is_hidden %} Hidden diff --git a/dribdat/templates/includes/pagination.html b/dribdat/templates/includes/pagination.html index fd8a9118..cf9ebf2e 100644 --- a/dribdat/templates/includes/pagination.html +++ b/dribdat/templates/includes/pagination.html @@ -2,13 +2,13 @@ {% if data.pages > 1 %} {% endif %} From 2d979f75ebf47cb8ebbebe30ef5c568350004f92 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Tue, 21 Mar 2023 23:20:53 +0100 Subject: [PATCH 07/23] Sync from a specific GitHub Markdown --- dribdat/aggregation.py | 2 ++ dribdat/apifetch.py | 26 +++++++++++++++++++++--- dribdat/templates/public/project.html | 29 ++++++++++++++++----------- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/dribdat/aggregation.py b/dribdat/aggregation.py index f5a53c4f..acab4419 100644 --- a/dribdat/aggregation.py +++ b/dribdat/aggregation.py @@ -58,6 +58,8 @@ def get_github_project(url): apiurl = apiurl[:-4] if apiurl == url: return {} + if apiurl.endswith('.md'): + return FetchWebProject(url) return FetchGithubProject(apiurl) diff --git a/dribdat/apifetch.py b/dribdat/apifetch.py index 9b8278fa..28f75cf3 100644 --- a/dribdat/apifetch.py +++ b/dribdat/apifetch.py @@ -297,6 +297,12 @@ def FetchWebProject(project_url): # Google Document if project_url.startswith('https://docs.google.com/document'): return FetchWebGoogleDoc(data.text, project_url) + # Instructables + elif project_url.startswith('https://www.instructables.com/'): + return FetchWebInstructables(data.text, project_url) + # GitHub Markdown + elif project_url.startswith('https://github.com/'): + return FetchWebGitHub(project_url) # CodiMD / HackMD elif data.text.find('
0: return FetchWebCodiMD(data.text, project_url) @@ -306,9 +312,6 @@ def FetchWebProject(project_url): # Etherpad elif data.text.find('pad.importExport.exportetherpad') > 0: return FetchWebEtherpad(data.text, project_url) - # Instructables - elif project_url.startswith('https://www.instructables.com/'): - return FetchWebInstructables(data.text, project_url) def FetchWebGoogleDoc(text, url): @@ -417,6 +420,23 @@ def FetchWebInstructables(text, url): return obj +def FetchWebGitHub(url): + """Grab a Markdown source from a GitHub link.""" + if not url.endswith('.md') or not '/blob/' in url: + return {} + filename = url.split('/')[-1].replace('.md', '') + rawurl = url.replace('/blob/', '/raw/') + rawdata = requests.get(rawurl, timeout=REQUEST_TIMEOUT) + text_content = rawdata.text or "" + return { + 'type': 'Markdown', + 'name': filename, + 'description': text_content, + 'source_url': url, + 'logo_icon': 'outdent', + } + + def ParseInstructablesPage(content): """Create an HTML summary of content.""" html_content = "" diff --git a/dribdat/templates/public/project.html b/dribdat/templates/public/project.html index ac8bc03f..23f0f383 100644 --- a/dribdat/templates/public/project.html +++ b/dribdat/templates/public/project.html @@ -208,19 +208,22 @@

{{project.name}}

@@ -274,19 +278,20 @@

{{project.name}}

{% if project.autotext %}
+ +
+ {{project.autotext|markdown}} +
+ {% if not project.event.lock_resources %} -
+
This content is a preview from an external site.
{% endif %} -
- {{project.autotext|markdown}} -
- - ▲ 🆙 + ▲▲▲
{% endif %} @@ -330,7 +335,7 @@

👥

{% endif %} -
+
{% if project.contact_url %} {% if project.contact_url.startswith('http') or project.contact_url.startswith('mailto:') %} 👋 Contact @@ -406,7 +411,7 @@

{{s.title}}

- ▲ 🆙 + ▲▲▲
{% endif %} From e635a9fbe47b14365fc51e7f16a2864359c69b85 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Tue, 21 Mar 2023 23:38:23 +0100 Subject: [PATCH 08/23] SSRF protect call --- dribdat/apifetch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dribdat/apifetch.py b/dribdat/apifetch.py index 28f75cf3..130e44dd 100644 --- a/dribdat/apifetch.py +++ b/dribdat/apifetch.py @@ -425,8 +425,8 @@ def FetchWebGitHub(url): if not url.endswith('.md') or not '/blob/' in url: return {} filename = url.split('/')[-1].replace('.md', '') - rawurl = url.replace('/blob/', '/raw/') - rawdata = requests.get(rawurl, timeout=REQUEST_TIMEOUT) + rawurl = url.replace('/blob/', '/raw/').replace("https://github.com/", '') + rawdata = requests.get("https://github.com/" + rawurl, timeout=REQUEST_TIMEOUT) text_content = rawdata.text or "" return { 'type': 'Markdown', From e031cf291981ef277540bc1849541948a6fd4a2b Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Wed, 22 Mar 2023 22:38:11 +0100 Subject: [PATCH 09/23] Project count, bump redis --- dribdat/templates/includes/eventhome.html | 2 +- dribdat/user/models.py | 2 +- poetry.lock | 12 ++++++------ requirements/prod.txt | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dribdat/templates/includes/eventhome.html b/dribdat/templates/includes/eventhome.html index e1e8b123..70bc71f5 100644 --- a/dribdat/templates/includes/eventhome.html +++ b/dribdat/templates/includes/eventhome.html @@ -39,7 +39,7 @@

{{ event.name }}

- {{ event.projects|count }} + {{ event.project_count }} {% if event.has_finished %} Results diff --git a/dribdat/user/models.py b/dribdat/user/models.py index 2c3817de..82b446e7 100644 --- a/dribdat/user/models.py +++ b/dribdat/user/models.py @@ -487,7 +487,7 @@ def project_count(self): """Return number of projects.""" if not self.projects: return 0 - return len(self.projects) + return self.projects.filter(not Project.is_hidden).count() def categories_for_event(self): """Event categories.""" diff --git a/poetry.lock b/poetry.lock index aa71862e..2795681a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -323,7 +323,7 @@ doc = ["sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "18.2.0" +version = "18.3.0" description = "Faker is a Python package that generates fake data for you." category = "dev" optional = false @@ -1232,7 +1232,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "redis" -version = "4.5.2" +version = "4.5.3" description = "Python client for Redis database and key-value store" category = "main" optional = false @@ -1895,8 +1895,8 @@ factory-boy = [ {file = "factory_boy-3.2.1.tar.gz", hash = "sha256:a98d277b0c047c75eb6e4ab8508a7f81fb03d2cb21986f627913546ef7a2a55e"}, ] faker = [ - {file = "Faker-18.2.0-py3-none-any.whl", hash = "sha256:20272ff42eaa3fd0a83d1c4ad27a6c0a58ebded4e48411a635b31643087d4bd6"}, - {file = "Faker-18.2.0.tar.gz", hash = "sha256:daf4fc907cdc009bfde011db845d9847e0293baa701c49607bac1184f11e5d95"}, + {file = "Faker-18.3.0-py3-none-any.whl", hash = "sha256:2deeee8fed3d1b8ae5f87d172d4569ddc859aab8693f7cd68eddc5d20400563a"}, + {file = "Faker-18.3.0.tar.gz", hash = "sha256:e7c058e1f360f245f265625b32d3189d7229398ad80a8b6bac459891745de052"}, ] flake8 = [] flake8-blind-except = [ @@ -2383,8 +2383,8 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] redis = [ - {file = "redis-4.5.2-py3-none-any.whl", hash = "sha256:4b12b3a1e9bfb43dc533330ec6d142329d0c27ea6bb6716a9d0389e8f2038a4e"}, - {file = "redis-4.5.2.tar.gz", hash = "sha256:d8ae1a4f725eea6e3958411870fdf944c587b00ea12be28d3bd5575d8f26430c"}, + {file = "redis-4.5.3-py3-none-any.whl", hash = "sha256:7df17a0a2b72a4c8895b462dd07616c51b1dcb48fdd7ecb7b6f4bf39ecb2e94e"}, + {file = "redis-4.5.3.tar.gz", hash = "sha256:56732e156fe31801c4f43396bd3ca0c2a7f6f83d7936798531b9848d103381aa"}, ] requests = [ {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, diff --git a/requirements/prod.txt b/requirements/prod.txt index 3ce2844b..3106ff44 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -73,7 +73,7 @@ python-dotenv==0.21.1; python_version >= "3.7" python-slugify==8.0.1; python_version >= "3.7" pytz==2022.7.1 pyyaml==5.4.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") -redis==4.5.2; python_version >= "3.7" +redis==4.5.3; python_version >= "3.7" requests-oauthlib==1.3.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" requests==2.28.2; python_version >= "3.7" and python_version < "4" rfc3986==2.0.0; python_version >= "3.7" From 7909bd3e56ddf02e5be5860e5710c0d1e5fa7a4a Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Wed, 22 Mar 2023 23:42:07 +0100 Subject: [PATCH 10/23] Bump pdf.js, lighter buttons --- dribdat/templates/render.html | 19 +++++++++++++++---- dribdat/user/models.py | 6 +++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dribdat/templates/render.html b/dribdat/templates/render.html index cdde1ea0..3f0e44f8 100644 --- a/dribdat/templates/render.html +++ b/dribdat/templates/render.html @@ -10,9 +10,9 @@ {% else %} {% endif %} - - - + + + @@ -47,8 +47,19 @@ } .btn-group { position: absolute; - right: 1px; + right: 4px; top: 1px; + font-size: 130%; + } + .btn-group button { + border-style: dashed; + background: transparent; + cursor: pointer; + opacity: 0.4; + } + .btn-group button:hover { + border-color: firebrick; + opacity: 1; } \ No newline at end of file diff --git a/dribdat/user/models.py b/dribdat/user/models.py index 82b446e7..0a5c577d 100644 --- a/dribdat/user/models.py +++ b/dribdat/user/models.py @@ -481,13 +481,13 @@ def status_text(self): return '' return status_text - @property def project_count(self): - """Return number of projects.""" + """Number of active projects in an event.""" if not self.projects: return 0 - return self.projects.filter(not Project.is_hidden).count() + return Project.query \ + .filter_by(event_id=self.id, is_hidden=False).count() def categories_for_event(self): """Event categories.""" From 13a049c349af796a39111608cab0fdb6cd25fe50 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Wed, 22 Mar 2023 23:55:45 +0100 Subject: [PATCH 11/23] Dashboard scheme --- dribdat/templates/public/dashboard.html | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/dribdat/templates/public/dashboard.html b/dribdat/templates/public/dashboard.html index d52ea432..41218832 100644 --- a/dribdat/templates/public/dashboard.html +++ b/dribdat/templates/public/dashboard.html @@ -42,13 +42,13 @@
-
+
{% if with_social_wall == 'twitter' %} -
+ {% elif with_social_wall == 'mastodon' %} - + {% endif %}
@@ -66,13 +66,13 @@ background-size: cover; background-repeat: no-repeat; background-image: url('{{ current_event.gallery_url }}'); + font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } .hidden { display: none; } #projects { - color: #fff; - font: 15pt sans-serif; + font-size: 14pt; overflow: hidden; list-style-type: none; padding: 0px; @@ -110,13 +110,18 @@ .container-fluid .sidebar { height: 100%; position: fixed; - background-color: black !important; - opacity: 0.7; + background-color: #ddd; + } + + .container-fluid .socialwall { + padding: 0px; + background: #fff; } #event-logo { - width: 175%; + width: 50%; margin-top: 100px; + position: fixed; } #event-logo img { border-radius: 10px; @@ -134,11 +139,12 @@ #schedule > * { display: none; } #announcements { - position: absolute; - top: 10%; + position: fixed; + top: 26%; left: 22%; /*left: 50%; margin-left: -15em;*/ + z-index: 999; } #announcements button { @@ -147,7 +153,7 @@ } #announcements textarea { - width: 12em; + width: 18em; height: 4em; font-size: 50px; font-weight: bold; @@ -159,8 +165,8 @@ border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='100' height='100' viewBox='0 0 100 100' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Cstyle%3Epath%7Banimation:stroke 5s infinite linear%3B%7D%40keyframes stroke%7Bto%7Bstroke-dashoffset:776%3B%7D%7D%3C/style%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='0%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%232d3561' /%3E%3Cstop offset='25%25' stop-color='%23c05c7e' /%3E%3Cstop offset='50%25' stop-color='%23f3826f' /%3E%3Cstop offset='100%25' stop-color='%23ffb961' /%3E%3C/linearGradient%3E %3Cpath d='M1.5 1.5 l97 0l0 97l-97 0 l0 -97' stroke-linecap='square' stroke='url(%23g)' stroke-width='3' stroke-dasharray='388'/%3E %3C/svg%3E") 1; } .superpowers { - position: absolute; - bottom: 0px; right: 0px; + position: fixed; + bottom: 0px; right: 50%; z-index: 999; } .superpowers button { @@ -231,6 +237,7 @@ $.getJSON('/api/event/current/projects.json', function(data) { $pp = $('#projects').empty(); data.projects.forEach(function(p) { + if (p.is_hidden) return; $pp.append('
' + ''+p.name+'' + '' + From 10d424bceb2d66d0e2736fb77432b936c0af8136 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Thu, 23 Mar 2023 00:07:08 +0100 Subject: [PATCH 12/23] Correct SRI hashes --- dribdat/templates/render.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dribdat/templates/render.html b/dribdat/templates/render.html index 3f0e44f8..cfbadfdf 100644 --- a/dribdat/templates/render.html +++ b/dribdat/templates/render.html @@ -10,9 +10,10 @@ {% else %} {% endif %} - - - + + + + From 6e8a72bf3205289b3bd83fb9197765e1aca116a6 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Thu, 23 Mar 2023 00:09:08 +0100 Subject: [PATCH 13/23] Correct SRI hashes --- dribdat/static/js/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dribdat/static/js/render.js b/dribdat/static/js/render.js index f4e303c7..435af8df 100644 --- a/dribdat/static/js/render.js +++ b/dribdat/static/js/render.js @@ -5,7 +5,7 @@ let $thecanvas = $('#canv'); let url = $thebody.attr('src'); let pdfjsLib = window["pdfjs-dist/build/pdf"]; -pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.0.279/pdf.worker.min.js'; +pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js'; var pdfDoc = null, pageNum = 1, From 963cd0b7abf0c1e95df6833bd32d260c9a3c7a68 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Thu, 23 Mar 2023 00:10:10 +0100 Subject: [PATCH 14/23] Correct SRI hashes --- dribdat/templates/render.html | 1 + 1 file changed, 1 insertion(+) diff --git a/dribdat/templates/render.html b/dribdat/templates/render.html index cfbadfdf..12fecf57 100644 --- a/dribdat/templates/render.html +++ b/dribdat/templates/render.html @@ -60,6 +60,7 @@ } .btn-group button:hover { border-color: firebrick; + background: white; opacity: 1; } From 5f5f6858501749468bb5a04f1ca41c08fcc255be Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Fri, 24 Mar 2023 10:19:14 +0100 Subject: [PATCH 15/23] Add Post button to nav, async mailer, faster participant view --- dribdat/aggregation.py | 15 ++++++++---- dribdat/mailer.py | 11 ++++----- dribdat/public/auth.py | 4 ++-- dribdat/static/js/editor.js | 1 - dribdat/static/js/notify.js | 2 +- dribdat/templates/includes/nav.html | 29 +++++++++++++++-------- dribdat/templates/public/projectpost.html | 2 +- 7 files changed, 37 insertions(+), 27 deletions(-) diff --git a/dribdat/aggregation.py b/dribdat/aggregation.py index acab4419..617a8c6c 100644 --- a/dribdat/aggregation.py +++ b/dribdat/aggregation.py @@ -14,6 +14,7 @@ ) import json import re +from sqlalchemy import and_ def GetProjectData(url): @@ -183,11 +184,15 @@ def GetEventUsers(event): return None users = [] userlist = [] - for p in event.projects: - for u in p.get_team(): - if u.id not in userlist: - userlist.append(u.id) - users.append(u) + projects = set([p.id for p in event.projects]) + activities = Activity.query.filter(and_( + Activity.name=='star', + Activity.project_id.in_(projects) + )).all() + for a in activities: + if a.user_id not in userlist: + userlist.append(a.user_id) + users.append(a.user) return sorted(users, key=lambda x: x.username) diff --git a/dribdat/mailer.py b/dribdat/mailer.py index 9b575d69..0738bb71 100644 --- a/dribdat/mailer.py +++ b/dribdat/mailer.py @@ -5,12 +5,7 @@ from dribdat.utils import random_password # noqa: I005 -async def send_async_email(app, msg): - with app.app_context(): - msg.send() - - -def user_activation(app, user): +async def user_activation(app, user): """Send an activation by e-mail.""" act_hash = random_password(32) with app.app_context(): @@ -29,4 +24,6 @@ def user_activation(app, user): + "Tap here to activate your account:\n\n%s" % act_url msg.to = [user.email] app.logger.info('Sending mail to user %d' % user.id) - send_async_email(app, msg) + await msg.send() + return True + diff --git a/dribdat/public/auth.py b/dribdat/public/auth.py index 42a90ecb..64065a46 100644 --- a/dribdat/public/auth.py +++ b/dribdat/public/auth.py @@ -70,7 +70,7 @@ def login(): @blueprint.route("/register/", methods=['GET', 'POST']) -def register(): +async def register(): """Register new user.""" if current_app.config['DRIBDAT_NOT_REGISTER']: flash("Registration currently not possible.", 'warning') @@ -106,7 +106,7 @@ def register(): new_user.active = False new_user.save() if current_app.config['MAIL_SERVER']: - user_activation(current_app, new_user) + await user_activation(current_app, new_user) flash("New accounts require activation. " + "Please click the dribdat link in your e-mail.", 'success') else: diff --git a/dribdat/static/js/editor.js b/dribdat/static/js/editor.js index 07ab5f69..59062cdd 100644 --- a/dribdat/static/js/editor.js +++ b/dribdat/static/js/editor.js @@ -85,7 +85,6 @@ vparent.show(); all_checked = $('.form-project-confirm input[type="checkbox"]:not(:checked)').length === 0; vinput.checked = all_checked; - // $('#next-level-hint').hide(); }); }); diff --git a/dribdat/static/js/notify.js b/dribdat/static/js/notify.js index 6210ec52..7228539d 100644 --- a/dribdat/static/js/notify.js +++ b/dribdat/static/js/notify.js @@ -40,7 +40,7 @@ // Check the location, ignore the Dashboard if (location.href.indexOf('/dashboard')<0) { createNotification(); - setInterval(createNotification, 30 * 1000); // check twice a minute + setInterval(createNotification, 60 * 1000); // check once a minute // To enable the popups, click the button in the footer $('#notification-button').show().click(function() { diff --git a/dribdat/templates/includes/nav.html b/dribdat/templates/includes/nav.html index 0c733b6d..6280a252 100644 --- a/dribdat/templates/includes/nav.html +++ b/dribdat/templates/includes/nav.html @@ -33,6 +33,14 @@ {% if event and not event.lock_resources %} + - {% endif %} {% if current_user and current_user.is_authenticated %} + {% if current_user.is_admin %} diff --git a/dribdat/templates/public/projectpost.html b/dribdat/templates/public/projectpost.html index b7e732fd..c3a86567 100644 --- a/dribdat/templates/public/projectpost.html +++ b/dribdat/templates/public/projectpost.html @@ -18,7 +18,7 @@

{% if all_valid %}

+ - - + {% for user in data.items %} + - - +
+ Dribs + Username
@@ -19,19 +22,17 @@

Users

- Created - - Dribs + Cre/Updated SSO Actions
{{ user.activity_count }} {% if user.is_admin %}
@@ -42,8 +43,7 @@

Users

{{ user.username }}
{{ user.created_at|format_date }}{{ user.activity_count }}{{ user.created_at|format_date }}
{{ user.updated_at|format_date }}
{% if user.sso_id %}✓{% endif %} From 40172ecb5f191c16592eef42e3d81e571581adc3 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Fri, 24 Mar 2023 12:03:13 +0100 Subject: [PATCH 17/23] Skip hidden projects in joined_projects --- dribdat/user/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dribdat/user/models.py b/dribdat/user/models.py index 0a5c577d..c80ce31a 100644 --- a/dribdat/user/models.py +++ b/dribdat/user/models.py @@ -175,12 +175,13 @@ def joined_projects(self, with_challenges=True, limit=-1): if limit < 0: activities = activities.all() else: - activities = activities.limit(limit) + activities = activities.limit(limit * 2) projects = [] for a in activities: if a.project not in projects and not a.project.is_hidden: if with_challenges or a.project.progress != 0: - projects.append(a.project) + if len(projects) < limit: + projects.append(a.project) return projects def posted_challenges(self): From a7746192e380832cb5a8fe2ff86a2d3936fe6c40 Mon Sep 17 00:00:00 2001 From: Oleg Lavrovsky Date: Mon, 27 Mar 2023 09:47:43 +0200 Subject: [PATCH 18/23] Alert notifier, Post button, py up --- dribdat/public/views.py | 2 +- dribdat/static/js/notify.js | 7 +++ dribdat/templates/includes/footer.html | 8 +-- dribdat/templates/includes/nav.html | 20 ++++---- poetry.lock | 68 ++++++++++++-------------- requirements/prod.txt | 2 +- 6 files changed, 55 insertions(+), 52 deletions(-) diff --git a/dribdat/public/views.py b/dribdat/public/views.py index d0449e4e..2c0ce950 100644 --- a/dribdat/public/views.py +++ b/dribdat/public/views.py @@ -154,7 +154,7 @@ def user(username): @login_required def user_post(): """Redirect to a Post form for my current project.""" - projects = current_user.joined_projects(False) + projects = current_user.joined_projects(True, 1) if not len(projects) > 0: flash('Please Join a project to be able to Post an update.', 'info') return redirect(url_for("public.home")) diff --git a/dribdat/static/js/notify.js b/dribdat/static/js/notify.js index 7228539d..6f55836e 100644 --- a/dribdat/static/js/notify.js +++ b/dribdat/static/js/notify.js @@ -45,6 +45,8 @@ // To enable the popups, click the button in the footer $('#notification-button').show().click(function() { //console.log('un-muting...'); + $('#notifications-status-text').html('You will now receive alerts'); + $('#global-notifications-alert').removeClass('hidden'); localStorage.removeItem('eventstatus-mute'); localStorage.removeItem('eventstatus'); createNotification(); @@ -52,4 +54,9 @@ } // -no-dashboard + // Close button is just a hider + $('#global-notifications-alert .close').click(function() { + $('#global-notifications-alert').addClass('hidden'); + }); + }).call(this, jQuery, window); diff --git a/dribdat/templates/includes/footer.html b/dribdat/templates/includes/footer.html index 4e43d0af..ac3ee557 100644 --- a/dribdat/templates/includes/footer.html +++ b/dribdat/templates/includes/footer.html @@ -1,8 +1,8 @@