forked from enviPath/enviPy
Added news entries
This commit is contained in:
+6
-1
@@ -3,7 +3,7 @@ from django.contrib import admin
|
|||||||
# Register your models here.
|
# Register your models here.
|
||||||
from .models import (
|
from .models import (
|
||||||
PESCompound,
|
PESCompound,
|
||||||
PESStructure
|
PESStructure, NewsEntry
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -15,5 +15,10 @@ class PESStructureAdmin(admin.ModelAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NewsEntryAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(PESCompound, PESCompoundAdmin)
|
admin.site.register(PESCompound, PESCompoundAdmin)
|
||||||
admin.site.register(PESStructure, PESStructureAdmin)
|
admin.site.register(PESStructure, PESStructureAdmin)
|
||||||
|
admin.site.register(NewsEntry, NewsEntryAdmin)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -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
@@ -1,10 +1,12 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
from uuid import uuid4
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import nh3
|
import nh3
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from epdb.models import (
|
from epdb.models import (
|
||||||
EnviPathModel,
|
EnviPathModel,
|
||||||
@@ -161,7 +163,6 @@ class PESCompound(Compound):
|
|||||||
normalized_structure=True,
|
normalized_structure=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
cs = PESStructure.create(
|
cs = PESStructure.create(
|
||||||
c,
|
c,
|
||||||
pes_url,
|
pes_url,
|
||||||
@@ -236,3 +237,23 @@ class PESStructure(CompoundStructure):
|
|||||||
"image": f"{reverse('depict_pes')}?pesLink={urllib.parse.quote(self.pes_link)}",
|
"image": f"{reverse('depict_pes')}?pesLink={urllib.parse.quote(self.pes_link)}",
|
||||||
"image_type": "png",
|
"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)
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -16,4 +16,5 @@ urlpatterns = [
|
|||||||
v.create_pes_node,
|
v.create_pes_node,
|
||||||
name="create pes node",
|
name="create pes node",
|
||||||
),
|
),
|
||||||
|
re_path(rf"^news/(?P<news_entry_uuid>{UUID})$", v.news_detail, name="news detail"),
|
||||||
]
|
]
|
||||||
|
|||||||
+13
-3
@@ -4,12 +4,12 @@ import logging
|
|||||||
import requests
|
import requests
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed
|
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.logic import PackageManager
|
||||||
from epdb.models import Pathway, Node, Group
|
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
|
from utilities.decorators import package_permission_required
|
||||||
|
|
||||||
Package = s.GET_PACKAGE_MODEL()
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
@@ -270,3 +270,13 @@ def visualize_pes(request):
|
|||||||
if rep.get('type') == 'color':
|
if rep.get('type') == 'color':
|
||||||
image_data = base64.b64decode(rep.get('base64').replace("data:image/png;base64,", ""))
|
image_data = base64.b64decode(rep.get('base64').replace("data:image/png;base64,", ""))
|
||||||
return HttpResponse(image_data, content_type="image/png")
|
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)
|
||||||
@@ -114,6 +114,7 @@ TEMPLATES = [
|
|||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"epdb.context_processors.package_context",
|
"epdb.context_processors.package_context",
|
||||||
|
"bayer.context_processors.news_entries_context"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional
|
|||||||
import jwt
|
import jwt
|
||||||
import nh3
|
import nh3
|
||||||
import requests
|
import requests
|
||||||
|
from django.db import transaction
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
@@ -2495,6 +2496,7 @@ class RegisterApplicationInSchema(Schema):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/integration", response={200: Any, 403: Error})
|
@router.post("/integration", response={200: Any, 403: Error})
|
||||||
|
@transaction.atomic
|
||||||
def register_app(request, app: Form[RegisterApplicationInSchema]):
|
def register_app(request, app: Form[RegisterApplicationInSchema]):
|
||||||
try:
|
try:
|
||||||
user = request.user
|
user = request.user
|
||||||
|
|||||||
@@ -260,15 +260,40 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Community News Section -->
|
<!-- 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">
|
<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">
|
<div id="services-container" class="flex justify-center gap-4">
|
||||||
<!-- News cards will be populated here -->
|
{% for ne in news_entries %}
|
||||||
<div id="loading" class="flex w-full justify-center">
|
<div class="card-body flex flex-col h-full justify-between p-5">
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<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>
|
</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>
|
||||||
|
|
||||||
<div class="mt-6 text-right">
|
<div class="mt-6 text-right">
|
||||||
@@ -280,9 +305,6 @@
|
|||||||
Read More Announcements
|
Read More Announcements
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Discourse API integration -->
|
|
||||||
<script src="{% static 'js/discourse-api.js' %}"></script>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user