Added news entries
API CI / api-tests (pull_request) Failing after 32s
CI / test (pull_request) Failing after 33s

This commit is contained in:
Tim Lorsbach
2026-10-21 09:53:58 +02:00
parent 5bbe316c24
commit 86c9ac887e
11 changed files with 204 additions and 24 deletions
+6 -1
View File
@@ -3,7 +3,7 @@ from django.contrib import admin
# Register your models here.
from .models import (
PESCompound,
PESStructure
PESStructure, NewsEntry
)
@@ -15,5 +15,10 @@ class PESStructureAdmin(admin.ModelAdmin):
pass
class NewsEntryAdmin(admin.ModelAdmin):
pass
admin.site.register(PESCompound, PESCompoundAdmin)
admin.site.register(PESStructure, PESStructureAdmin)
admin.site.register(NewsEntry, NewsEntryAdmin)
+25
View File
@@ -0,0 +1,25 @@
"""
Context processors for enviPy application.
Context processors automatically make variables available to all templates.
"""
from django.utils import timezone
from .models import NewsEntry
def news_entries_context(request):
"""
Provides package data for the search modal which is included globally
in framework_modern.html.
Returns:
dict: Context dictionary with reviewed and unreviewed packages
"""
qs = NewsEntry.objects.filter(publication_date__lte=timezone.now()).order_by('-publication_date')[:3]
return {
"news_entries": qs,
}
+29
View File
@@ -0,0 +1,29 @@
# Generated by Django 6.0.3 on 2026-10-16 07:50
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bayer', '0004_package_shareable'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='NewsEntry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('url', models.TextField(null=True, unique=True, verbose_name='URL')),
('title', models.TextField(verbose_name='Title')),
('publication_date', models.DateTimeField(verbose_name='Date')),
('content', models.TextField(verbose_name='Content')),
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
]
@@ -0,0 +1,17 @@
# Generated by Django 6.0.3 on 2026-10-16 07:59
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('bayer', '0005_newsentry'),
]
operations = [
migrations.RemoveField(
model_name='newsentry',
name='url',
),
]
+22 -1
View File
@@ -1,10 +1,12 @@
from typing import List
from uuid import uuid4
import urllib.parse
import nh3
from django.conf import settings as s
from django.db import models, transaction
from django.db.models import QuerySet
from django.urls import reverse
from django.utils import timezone
from epdb.models import (
EnviPathModel,
@@ -161,7 +163,6 @@ class PESCompound(Compound):
normalized_structure=True,
)
cs = PESStructure.create(
c,
pes_url,
@@ -236,3 +237,23 @@ class PESStructure(CompoundStructure):
"image": f"{reverse('depict_pes')}?pesLink={urllib.parse.quote(self.pes_link)}",
"image_type": "png",
}
class NewsEntry(models.Model):
uuid = models.UUIDField(
null=False, blank=False, verbose_name="UUID of this object", unique=True, default=uuid4
)
title = models.TextField(verbose_name="Title")
publication_date = models.DateTimeField(verbose_name="Date", default=timezone.now)
content = models.TextField(verbose_name="Content")
author = models.ForeignKey("epdb.User", on_delete=models.SET_NULL, blank=True, null=True)
@property
def url(self):
return f"{s.SERVER_URL}/news/{self.uuid}"
def save(self, *args, **kwargs):
if not self.url:
self.url = self._url()
super().save(*args, **kwargs)
+47
View File
@@ -0,0 +1,47 @@
{% extends "framework_modern.html" %}
{% block content %}
<div class="mx-auto max-w-4xl px-4 py-8">
{# Header / Title row #}
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">{{ news_entry.title }}</h1>
</div>
{# Meta info card #}
<div class="card bg-base-100 shadow mb-6">
<div class="card-body">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<span class="text-sm font-semibold text-base-content/60">Published</span>
<p class="mt-1">{{ news_entry.publication_date|date:"N j, Y, P" }}</p>
</div>
<div>
<span class="text-sm font-semibold text-base-content/60">Author</span>
<p class="mt-1">
{% if news_entry.author %}
{{ news_entry.author.get_full_name|default:news_entry.author.username }}
{% else %}
<span class="text-base-content/40 italic">Unknown</span>
{% endif %}
</p>
</div>
</div>
</div>
</div>
{# Content card #}
<div class="card bg-base-100 shadow">
<div class="card-body prose max-w-none">
{{ news_entry.content|safe }}
</div>
</div>
{# Back link #}
<div class="mt-6">
<a href="javascript:history.back()" class="btn btn-ghost btn-sm">
← Back
</a>
</div>
</div>
{% endblock content %}
+1
View File
@@ -16,4 +16,5 @@ urlpatterns = [
v.create_pes_node,
name="create pes node",
),
re_path(rf"^news/(?P<news_entry_uuid>{UUID})$", v.news_detail, name="news detail"),
]
+13 -3
View File
@@ -4,12 +4,12 @@ import logging
import requests
from django.conf import settings as s
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed
from django.shortcuts import redirect
from django.shortcuts import redirect, render
from bayer.models import PESCompound
from bayer.models import PESCompound, NewsEntry
from epdb.logic import PackageManager
from epdb.models import Pathway, Node, Group
from epdb.views import _anonymous_or_real, error
from epdb.views import _anonymous_or_real, get_base_context, error
from utilities.decorators import package_permission_required
Package = s.GET_PACKAGE_MODEL()
@@ -270,3 +270,13 @@ def visualize_pes(request):
if rep.get('type') == 'color':
image_data = base64.b64decode(rep.get('base64').replace("data:image/png;base64,", ""))
return HttpResponse(image_data, content_type="image/png")
@package_permission_required()
def news_detail(request, news_entry_uuid):
current_user = _anonymous_or_real(request)
context = get_base_context(request, current_user)
context["entry"] = NewsEntry.objects.get(uuid=news_entry_uuid)
return render(request, "objects/news_entry.html", context)
+1
View File
@@ -114,6 +114,7 @@ TEMPLATES = [
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"epdb.context_processors.package_context",
"bayer.context_processors.news_entries_context"
],
},
},
+2
View File
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional
import jwt
import nh3
import requests
from django.db import transaction
from django.conf import settings as s
from django.contrib.auth import get_user_model
from django.core.cache import cache
@@ -2495,6 +2496,7 @@ class RegisterApplicationInSchema(Schema):
@router.post("/integration", response={200: Any, 403: Error})
@transaction.atomic
def register_app(request, app: Form[RegisterApplicationInSchema]):
try:
user = request.user
+31 -9
View File
@@ -260,15 +260,40 @@
</div>
<!-- Community News Section -->
<section class="bg-base-200 z-10 mx-8 py-16">
<section class="bg-base-200 z-10 mx-8 py-6">
<div class="mx-auto max-w-7xl px-4">
<h2 class="h2 mb-8 text-left font-bold">Community Updates</h2>
<h2 class="h2 mb-8 text-left font-bold">News</h2>
<div id="community-news-container" class="flex justify-center gap-4">
<!-- News cards will be populated here -->
<div id="loading" class="flex w-full justify-center">
<span class="loading loading-spinner loading-lg"></span>
<div id="services-container" class="flex justify-center gap-4">
{% for ne in news_entries %}
<div class="card-body flex flex-col h-full justify-between p-5">
<div>
<div class="flex items-center gap-2 mb-2">
<div class="bg-primary/10 text-primary rounded-lg p-1.5">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
</div>
<h3 class="card-title text-base font-semibold tracking-tight">
{{ ne.title }} - {{ ne.publication_date|date }}
</h3>
</div>
<p class="text-sm text-base-content/60 leading-snug line-clamp-4">
{{ ne.content }}
</p>
</div>
<a href="{{ ne.url }}" class="btn btn-primary btn-sm w-full group-hover:btn-active mt-1">
Read Full
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
</a>
</div>
{% endfor %}
</div>
<div class="mt-6 text-right">
@@ -280,9 +305,6 @@
Read More Announcements
</a>
</div>
<!-- Discourse API integration -->
<script src="{% static 'js/discourse-api.js' %}"></script>
</div>
</section>