Compare commits

..

9 Commits

Author SHA1 Message Date
markusro c08bdc3384 moved settings from devel to production 2026-04-14 13:46:51 +02:00
markusro d481b8ee0e Merge pull request 'container' (#2) from container into main
Reviewed-on: #2
2026-04-14 11:03:48 +00:00
markusro 945ad3baf8 Merge branch 'main' into container 2026-04-14 11:03:36 +00:00
markusro b4f39381e3 Moved to uv packaging 2026-04-14 13:02:01 +02:00
markusro a953bce90e fixed permissions for db.sqlite3 2026-04-14 11:43:35 +02:00
mhjenbaz ed23bd8868 update 2026-04-14 10:59:40 +02:00
mhjenbaz 7eb26ad173 update 2026-04-14 10:54:01 +02:00
mhjenbaz c453f617e8 update 2026-04-14 10:52:49 +02:00
markusro 0b979cfada Initial container build 2026-04-14 08:40:14 +02:00
12 changed files with 298 additions and 102 deletions
+4
View File
@@ -0,0 +1,4 @@
venv
.venv
__pycache__
.idea
+63
View File
@@ -0,0 +1,63 @@
# Use the official Python runtime image
FROM python:3.13-alpine
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
RUN apk add --no-cache sqlite
# Set the working directory inside the container
WORKDIR /app
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
--disabled-password \
--shell "/sbin/nologin" \
--uid "${UID}" \
appuser
# Set environment variables
## Python
# Prevents Python from writing pyc files to disk
ENV PYTHONDONTWRITEBYTECODE=1
# Prevents Python from buffering stdout and stderr
ENV PYTHONUNBUFFERED=1
# Ignore pip warning about running as root
ENV PIP_ROOT_USER_ACTION=ignore
## uv
# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1
# Copy from the cache instead of linking since it's a mounted volume
ENV UV_LINK_MODE=copy
# Disable development dependencies
ENV UV_NO_DEV=1
# copy project settings
COPY pyproject.toml uv.lock /app/
# Install the project's dependencies using the lockfile and settings
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-install-project
RUN chown appuser:appuser /app
# Copy the Django project files to the container
COPY --chown=appuser:appuser excel_mimic /app/excel_mimic
COPY --chown=appuser:appuser sheets /app/sheets
COPY --chown=appuser:appuser manage.py db.sqlite3 /app/
# Expose the Django port
EXPOSE 8000
# Switch to the non-privileged user to run the application.
USER appuser
# Allow all hosts
ENV DJANGO_ALLOWED_HOSTS=*
# Run Djangos development server
CMD ["uv", "run", "manage.py", "runserver", "0.0.0.0:8000"]
+11
View File
@@ -1,3 +1,14 @@
# Build new container
podman build . -t he-database
podman tag he-database gitea.pkm.physik.tu-darmstadt.de/markusro/he-database:latest
podman push gitea.pkm.physik.tu-darmstadt.de/markusro/he-database:latest
# Run and update container
Also check documentation from [RedHat](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_atomic_host/7/html/managing_containers/running_containers_as_systemd_services_with_podman).
podman pull gitea.pkm.physik.tu-darmstadt.de/markusro/he-database:latest
podman run --replace --detach --name he-database -p 8000:8000 he-database:latest
# Create DB Schema in chartdb.io
1. go to https://app.chartdb.io
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -23,9 +23,9 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-qsqw7#kmvlyc1ou5emdh8^_bqnvp+hui(4w#1yy4ui82+p=%p*'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = False
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["*"]
# Application definition
+9
View File
@@ -0,0 +1,9 @@
[project]
name = "he-database"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"django==6.0.4",
]
+1 -1
View File
@@ -146,7 +146,7 @@
<body>
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
&#8678; Go to Clients
&#8678; Zur Startseite
</a>
<h2>Betriebskosten</h2>
+9 -9
View File
@@ -8,7 +8,7 @@
{% csrf_token %}
<h3>Allgemeines 6-Monats-Intervall</h3>
<label>Year:</label>
<label>Jahr:</label>
<select name="year">
{% for y in available_years %}
<option value="{{ y }}" {% if y == interval_year %}selected{% endif %}>
@@ -17,7 +17,7 @@
{% endfor %}
</select>
<label>Start Month:</label>
<label>Startmonat:</label>
<select name="start_month">
<option value="1" {% if interval_start_month == 1 %}selected{% endif %}>01</option>
<option value="2" {% if interval_start_month == 2 %}selected{% endif %}>02</option>
@@ -33,13 +33,13 @@
<option value="12" {% if interval_start_month == 12 %}selected{% endif %}>12</option>
</select>
<button type="submit">Apply Interval</button>
<button type="submit">Intervall anwenden</button>
</form>
<!-- Year Filter -->
<div class="year-filter">
<label for="year-select">Year:</label>
<label for="year-select">Jahr:</label>
<select id="year-select" onchange="window.location.href='?year='+this.value">
{% for year in available_years %}
<option value="{{ year }}" {% if year == current_year %}selected{% endif %}>
@@ -73,7 +73,7 @@
</tr>
{% endfor %}
<tr class="total">
<td><strong>Σ (all clients, selected 6-month interval)</strong></td>
<td><strong>Σ (alle Kunden, ausgewähltes 6-Monats-Intervall)</strong></td>
{% for month in months %}
<td>-</td>
@@ -86,12 +86,12 @@
<!-- Navigation Buttons -->
<div class="navigation-buttons">
<a href="{% url 'table_one' %}" class="nav-button">Go to Helium Input</a>
<a href="{% url 'table_two' %}" class="nav-button">Go to Helium Output</a>
<a href="/admin/" class="nav-button admin-button">Go to Admin Panel</a>
<a href="{% url 'table_one' %}" class="nav-button">Heliumrückgabe</a>
<a href="{% url 'table_two' %}" class="nav-button">Heliumausgabe</a>
<a href="/admin/" class="nav-button admin-button">Admin</a>
<a href="{% url 'betriebskosten_list' %}" class="nav-button">Betriebskosten</a>
<a href="{% url 'monthly_sheet' year=2024 month=1 %}" class="nav-button">Monthly Sheets</a>
<a href="{% url 'monthly_sheet' year=2024 month=1 %}" class="nav-button">Monatsbilanz</a>
<a href="{% url 'halfyear_balance' %}" class="nav-button">Halbjahres Bilanz</a>
<!-- ✅ NEW -->
+92 -38
View File
@@ -4,27 +4,43 @@
<div class="spreadsheet-container">
<!-- Navigation Header -->
<div class="sheet-navigation">
{# Previous month link #}
{% with pm=prev_month %}
{% if pm.year and pm.month %}
<a href="{% url 'monthly_sheet' pm.year pm.month %}">← Previous</a>
{% else %}
<span class="disabled-link">← Previous</span>
{% endif %}
{% endwith %}
<div class="nav-left">
<a href="{% url 'clients_list' %}" class="main-page-link">🏠 Hauptseite</a>
<h2>{{ year }} - {{ month_name }} (Sheet {{ month }}/6)</h2>
{% with pm=prev_month %}
{% if pm.year and pm.month %}
<a href="{% url 'monthly_sheet' pm.year pm.month %}">← Vorherigen Monat</a>
{% else %}
<span class="disabled-link">← Vorherigen Monat</span>
{% endif %}
{% endwith %}
</div>
{# Next month link #}
{% with nm=next_month %}
{% if nm.year and nm.month %}
<a href="{% url 'monthly_sheet' nm.year nm.month %}">Next →</a>
{% else %}
<span class="disabled-link">Next →</span>
{% endif %}
{% endwith %}
<div class="nav-center">
<h2>{{ year }} - {{ month_name }} (Sheet {{ month }}/6)</h2>
</div>
<a href="{% url 'summary_sheet' year month %}">Go to Summary</a>
<div class="nav-right">
{% with nm=next_month %}
{% if nm.year and nm.month %}
<a href="{% url 'monthly_sheet' nm.year nm.month %}">Nächsten Monat →</a>
{% else %}
<span class="disabled-link">Nächsten Monat →</span>
{% endif %}
{% endwith %}
<div class="sheet-jump-box">
<label for="jump-year">Jahr:</label>
<input type="number" id="jump-year" min="2000" max="2100" value="{{ year }}">
<label for="jump-month">Monat:</label>
<input type="number" id="jump-month" min="1" max="12" value="{{ month }}">
<button type="button" id="jump-sheet-btn" class="btn btn-primary">Anwenden</button>
</div>
</div>
</div>
<!-- Top Tables Container -->
@@ -32,11 +48,11 @@
<div class="top-tables">
<!-- Left Table (18 rows × clients) -->
<div class="table-container top-left-table">
<h3>Table 1: Top Left</h3>
<h3>Table 1: Oben Links</h3>
<table class="spreadsheet-table">
<thead>
<tr>
<th>Row Label</th>
<th>Zeilenbezeichnung</th>
{% for header in top_left_headers %}
<th>{{ header }}</th>
{% endfor %}
@@ -129,11 +145,11 @@
<!-- Right Table (24 rows × 6 clients) -->
<!-- Update the top-right table section in monthly_sheet.html -->
<div class="table-container top-right-table">
<h3>Table 2: Top Right</h3>
<h3>Table 2: Oben Rechts</h3>
<table class="spreadsheet-table">
<thead>
<tr>
<th>Row Label</th>
<th>Zeilenbezeichnung</th>
{% for header in top_right_headers %}
<th>{{ header }}</th>
{% endfor %}
@@ -236,11 +252,11 @@
</table>
</div>
<div class="table-container overall-summary-table">
<h3>Gesamtsumme (Top Left + Top Right)</h3>
<h3>Gesamtsumme (Oben Links+Rechts)</h3>
<table class="spreadsheet-table">
<thead>
<tr>
<th>Row Label</th>
<th>Zeilenbezeichnung</th>
<th>Σ</th>
</tr>
</thead>
@@ -264,7 +280,7 @@
<!-- Bottom Tables -->
<div class="bottom-tables">
<div class="table-container bottom-table-1">
<h3>Bottom Table 1 Gasspeicher</h3>
<h3>Untere Tabelle 1 Gasspeicher</h3>
@@ -373,7 +389,7 @@
<div class="table-container bottom-table-2">
<h3>Bottom Table 2 Verbraucherbestand L-He</h3>
<h3>Untere Tabelle 2 Verbraucherbestand L-He</h3>
<table class="spreadsheet-table">
<tbody>
@@ -450,7 +466,7 @@
</div>
<div class="table-container bottom-table-3">
<h3>Bottom Table 3 Bilanz</h3>
<h3>Untere Tabelle 3 Bilanz</h3>
<table class="spreadsheet-table">
<thead>
@@ -621,17 +637,7 @@
</div>
<!-- Save Button -->
<div class="action-bar">
<button id="save-all-btn" class="btn btn-primary">Save All Cells</button>
<div id="save-status"></div>
<div class="legend">
<small>
<span style="background-color: #d1ecf1; padding: 2px 5px;">Saved cells</span>
<span style="background-color: #d4edda; padding: 2px 5px;">Calculated cells</span>
</small>
</div>
</div>
</div>
<!-- Hidden form for cell data -->
@@ -645,16 +651,48 @@
padding: 20px;
}
.nav-left,
.nav-center,
.nav-right {
display: flex;
align-items: center;
gap: 12px;
}
.nav-center h2 {
margin: 0;
}
.main-page-link {
font-weight: bold;
text-decoration: none;
color: #007bff;
}
.sheet-jump-box {
display: flex;
align-items: center;
gap: 8px;
margin-left: 10px;
}
.sheet-jump-box input {
width: 80px;
padding: 6px;
}
.sheet-navigation {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
margin-bottom: 20px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
flex-wrap: wrap;
}
.sheet-navigation h2 {
margin: 0;
}
@@ -821,6 +859,22 @@
<script>
$(document).ready(function() {
$('#jump-sheet-btn').on('click', function() {
const year = parseInt($('#jump-year').val(), 10);
const month = parseInt($('#jump-month').val(), 10);
if (isNaN(year) || isNaN(month)) {
alert('Bitte Jahr und Monat eingeben.');
return;
}
if (month < 1 || month > 12) {
alert('Monat muss zwischen 1 und 12 liegen.');
return;
}
window.location.href = '/sheet/' + year + '/' + month + '/';
});
// ---------- Helpers ----------
+25 -25
View File
@@ -286,21 +286,21 @@
<!-- This link should ONLY wrap the text below -->
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
&#8678; Go to Clients
&#8678; Zur Startseite
</a>
<!-- 6-Month overview card (OUTSIDE any <a>) -->
<div class="overview-card">
<div class="overview-title">Helium Input 6 Month Overview</div>
<div class="overview-title">Heliumrückgabe Überblick über 6 Monate</div>
{% if overview %}
<div class="overview-subtitle">
Period:
Interval
<strong>
{{ overview.start_month }}/{{ overview.start_year }}
{{ overview.end_month }}/{{ overview.end_year }}
</strong>
(selected on the main page)
(ausgewählt am Start Seite)
</div>
<table class="overview-table">
@@ -310,7 +310,7 @@
{% for g in overview.groups %}
<th>{{ g.label }}</th>
{% endfor %}
<th>Month total</th>
<th>Monat total</th>
</tr>
</thead>
<tbody>
@@ -342,7 +342,7 @@
<h2>Helium Input</h2>
<div class="table-container">
<button class="add-row-btn" id="add-row-one">Add Row</button>
<button class="add-row-btn" id="add-row-one">Eingabe hinzufügen</button>
<table id="table-one">
<colgroup>
<col style="width: 3%"> <!-- # -->
@@ -367,7 +367,7 @@
<th>#</th>
<th>ID</th>
<th>Institute</th>
<th>Client</th>
<th>Kunde</th>
<th>Druck</th>
<th>Reinheit</th>
<th>Druckkorrektur</th>
@@ -377,8 +377,8 @@
<th>L-He</th>
<th>L-He zus.</th>
<th>L-He ges.</th>
<th>Date</th>
<th>Month</th>
<th>Datum</th>
<th>Monat</th>
<th>Actions</th>
</tr>
</thead>
@@ -409,8 +409,8 @@
{% endif %}
</td>
<td class="actions">
<button class="edit-btn-one">Edit</button>
<button class="delete-btn-one">Delete</button>
<button class="edit-btn-one">Bearbeiten</button>
<button class="delete-btn-one">Löschen</button>
</td>
</tr>
{% endfor %}
@@ -522,7 +522,7 @@
<!-- Client Selection -->
<label for="edit-client-id">Kunde:</label>
<select id="edit-client-id" disabled>
<option value="">Select Institute first</option>
<option value="">Institut erstmal auswählen</option>
{% for client in clients %}
<option value="{{ client.id }}" data-institute="{{ client.institute.id }}">{{ client.name }}</option>
{% endfor %}
@@ -708,19 +708,19 @@
const instituteId = $('#add-institute-id').val();
if (!instituteId) {
alert('Please select an institute');
alert('Institut erstmal auswählen');
return;
}
if (!clientId) {
alert('Please select a client');
alert('Kunde erstmal auswählen');
return;
}
// Validate date first
let dateInput = $('#add-date').val();
if (!dateInput) {
alert('Please select a date');
alert('Datum erstmal auswählen');
return;
}
@@ -732,17 +732,17 @@
let gesFlaschInhalt = parseFloat($('#add-constant-300').val()) || 300.0;
if (isNaN(pressure) || pressure < 0) {
alert('Please enter a valid pressure value');
alert('Bitte geben Sie einen gültigen Druckwert ein.');
return;
}
if (isNaN(purity) || purity < 0 || purity > 100) {
alert('Please enter a valid purity value (0-100)');
alert('Bitte geben Sie einen gültigen Reinheitswert ein (0-100).');
return;
}
if (isNaN(druckkorrektur) || druckkorrektur < 0) {
alert('Please enter a valid Druckkorrektur value');
alert('Bitte geben Sie einen gültigen Druckkorrekturwert ein');
return;
}
@@ -875,12 +875,12 @@
const instituteId = $('#edit-institute-id').val();
if (!instituteId) {
alert('Please select an institute');
alert('Institut erstmal auswählen');
return;
}
if (!clientId) {
alert('Please select a client');
alert('Kunde erstmal auswählen');
return;
}
@@ -892,17 +892,17 @@
let gesFlaschInhalt = parseFloat($('#edit-constant-300').val()) || 300.0;
if (isNaN(pressure) || pressure < 0) {
alert('Please enter a valid pressure value');
alert('Bitte geben Sie einen gültigen Druckwert ein.');
return;
}
if (isNaN(purity) || purity < 0 || purity > 100) {
alert('Please enter a valid purity value (0-100)');
alert('Bitte geben Sie einen gültigen Reinheitswert ein (0-100).');
return;
}
if (isNaN(druckkorrektur) || druckkorrektur < 0) {
alert('Please enter a valid Druckkorrektur value');
alert('Bitte geben Sie einen gültigen Druckkorrekturwert ein');
return;
}
@@ -937,7 +937,7 @@
// Validate inputs
if (!formData.date) {
alert('Please select a date');
alert('Bitte wählen Sie ein Datum.');
return;
}
@@ -993,7 +993,7 @@
}
},
error: function () {
alert('Failed to delete entry. Please try again.');
alert('Eintrag konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.');
}
});
});
+27 -27
View File
@@ -190,28 +190,28 @@
<div class="d-flex justify-content-start mb-2">
<!-- "Go to Clients" button at top-left -->
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
&#8678; Go to Clients
&#8678; Zur Startseite
</a>
</div>
<h2>LHe Dewar Output</h2>
<div class="table-container">
<button class="add-row-btn" id="add-row-two">Add Output</button>
<button class="add-row-btn" id="add-row-two">Eingabe hinzufügen</button>
<table id="table-two">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th>Institute</th>
<th>Client</th>
<th>Date</th>
<th>Institut</th>
<th>Kunde</th>
<th>Datum</th>
<th>Warm</th>
<th>LHe Delivery</th>
<th>LHe Anlieferung</th>
<th>Vor</th>
<th>Nach</th>
<th>LHe Output</th>
<th>Notes</th>
<th>LHe Ausgabe</th>
<th>Notizen</th>
<th>Actions</th>
</tr>
</thead>
@@ -230,8 +230,8 @@
<td>{% if entry.lhe_output is not None %}{{ entry.lhe_output|floatformat:1 }}{% endif %}</td>
<td>{{ entry.notes }}</td>
<td class="actions">
<button class="edit-btn-two">Edit</button>
<button class="delete-btn-two">Delete</button>
<button class="edit-btn-two">Bearbeiten</button>
<button class="delete-btn-two">Löschen</button>
</td>
</tr>
{% endfor %}
@@ -245,24 +245,24 @@
<h3>LHe Dewar Output</h3>
<!-- Institute Selection -->
<label for="add-institute-id">Institute:</label>
<label for="add-institute-id">Institut:</label>
<select id="add-institute-id">
<option value="">Select Institute</option>
<option value="">Institut auswählen</option>
{% for institute in institutes %}
<option value="{{ institute.id }}">{{ institute.name }}</option>
{% endfor %}
</select>
<!-- Client Selection (will be populated based on institute) -->
<label for="add-client-id">Client:</label>
<label for="add-client-id">Kunde:</label>
<select id="add-client-id" disabled>
<option value="">Select Institute first</option>
<option value="">Institut erstaml auswählen</option>
{% for client in clients %}
<option value="{{ client.id }}" data-institute="{{ client.institute.id }}">{{ client.name }}</option>
{% endfor %}
</select>
<label for="add-date">Date:</label>
<label for="add-date">Datum:</label>
<input type="date" id="add-date">
<!-- Changed from checkbox to number input -->
@@ -273,7 +273,7 @@
<div class="input-with-label">
<label for="add-lhe-delivery">LHe Anlieferung:</label>
<input type="text" id="add-lhe-delivery" placeholder="Enter delivery amount">
<input type="text" id="add-lhe-delivery" placeholder="Wert eingeben">
</div>
<div class="input-with-label">
@@ -291,8 +291,8 @@
<input type="number" id="add-lhe-output" readonly>
</div>
<label for="add-notes">Notes:</label>
<textarea id="add-notes" placeholder="Additional notes"></textarea>
<label for="add-notes">Notizen:</label>
<textarea id="add-notes" placeholder="Notizen"></textarea>
<div class="popup-buttons">
<button class="save-btn" id="save-add-two">Save</button>
@@ -310,16 +310,16 @@
<!-- Institute Selection -->
<label for="edit-institute-id">Institute:</label>
<select id="edit-institute-id">
<option value="">Select Institute</option>
<option value="">Institut Auswälen</option>
{% for institute in institutes %}
<option value="{{ institute.id }}">{{ institute.name }}</option>
{% endfor %}
</select>
<!-- Client Selection (will be populated based on institute) -->
<label for="edit-client-id">Client:</label>
<label for="edit-client-id">Kunde:</label>
<select id="edit-client-id" disabled>
<option value="">Select Institute first</option>
<option value="">Institut erstmal Auswälen</option>
{% for client in clients %}
<option value="{{ client.id }}" data-institute="{{ client.institute.id }}">{{ client.name }}</option>
{% endfor %}
@@ -336,7 +336,7 @@
<div class="input-with-label">
<label for="edit-lhe-delivery">LHe Anlieferung:</label>
<input type="text" id="edit-lhe-delivery" placeholder="Enter delivery amount">
<input type="text" id="edit-lhe-delivery" placeholder="Wert eingeben">
</div>
<div class="input-with-label">
@@ -399,7 +399,7 @@
function filterClients(instituteId, targetSelect, allOptions) {
if (!instituteId) {
// Show only the default option if no institute selected
targetSelect.html('<option value="">Select Institute first</option>');
targetSelect.html('<option value="">erstmal Institut auswählen</option>');
targetSelect.prop('disabled', true);
} else {
// Restore all options first
@@ -412,7 +412,7 @@
targetSelect.find('option').hide();
// Always show the "Select Client" option
targetSelect.find('option[value=""]').show().text('Select Client');
targetSelect.find('option[value=""]').show().text('Kunde Auswählen');
// Show only clients from selected institute
const clientsFromInstitute = targetSelect.find(`option[data-institute="${instituteId}"]`);
@@ -473,7 +473,7 @@
$('#add-row-two').on('click', function () {
// Reset form
$('#add-institute-id').val('');
$('#add-client-id').html('<option value="">Select Institute first</option>');
$('#add-client-id').html('<option value="">Erstaml institut auswählen</option>');
$('#add-client-id').prop('disabled', true);
$('#add-date').val('');
$('#add-is-warm').val('0');
@@ -645,12 +645,12 @@
const instituteId = $('#edit-institute-id').val();
if (!instituteId) {
alert('Please select an institute');
alert('Institut erstmal auswählen');
return;
}
if (!clientId) {
alert('Please select a client');
alert('Kunde erstmal auswählen');
return;
}
Generated
+55
View File
@@ -0,0 +1,55 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "asgiref"
version = "3.11.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" },
]
[[package]]
name = "django"
version = "6.0.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/b9/4155091ad1788b38563bd77a7258c0834e8c12a7f56f6975deaf54f8b61d/django-6.0.4.tar.gz", hash = "sha256:8cfa2572b3f2768b2e84983cf3c4811877a01edb64e817986ec5d60751c113ac", size = 10907407, upload-time = "2026-04-07T13:55:44.961Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/47/3d61d611609764aa71a37f7037b870e7bfb22937366974c4fd46cada7bab/django-6.0.4-py3-none-any.whl", hash = "sha256:14359c809fc16e8f81fd2b59d7d348e4d2d799da6840b10522b6edf7b8afc1da", size = 8368342, upload-time = "2026-04-07T13:55:37.999Z" },
]
[[package]]
name = "he-database"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "django" },
]
[package.metadata]
requires-dist = [{ name = "django", specifier = "==6.0.4" }]
[[package]]
name = "sqlparse"
version = "0.5.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
]
[[package]]
name = "tzdata"
version = "2026.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" },
]