Working .env setup
This commit is contained in:
@@ -1,3 +1,15 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
from .models import ApiToken, UserProfile
|
||||
|
||||
|
||||
@admin.register(UserProfile)
|
||||
class UserProfileAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'joined')
|
||||
search_fields = ('user__username',)
|
||||
|
||||
|
||||
@admin.register(ApiToken)
|
||||
class ApiTokenAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'label', 'key_prefix', 'created_at', 'last_used')
|
||||
search_fields = ('user__username', 'label')
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 6.0.3 on 2026-08-03 14:54
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ApiToken',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('token', models.TextField()),
|
||||
('key_prefix', models.CharField(db_index=True, max_length=8)),
|
||||
('label', models.CharField(default='API token', max_length=64)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('last_used', models.DateTimeField(blank=True, null=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserProfile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('bio', models.TextField(blank=True, default='')),
|
||||
('avatar', models.ImageField(blank=True, null=True, upload_to='profiles/%d/')),
|
||||
('joined', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='userprofile', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,3 +1,36 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class UserProfile(models.Model):
|
||||
user = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='userprofile',
|
||||
)
|
||||
bio = models.TextField(blank=True, default='')
|
||||
avatar = models.ImageField(
|
||||
upload_to='profiles/%d/',
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
joined = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} profile'
|
||||
|
||||
|
||||
class ApiToken(models.Model):
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='api_tokens',
|
||||
)
|
||||
token = models.TextField()
|
||||
key_prefix = models.CharField(max_length=8, db_index=True)
|
||||
label = models.CharField(max_length=64, default='API token')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
last_used = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} - {self.label} ({self.key_prefix}...)'
|
||||
@@ -0,0 +1,13 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Login - Packs{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Login</h1>
|
||||
<form method="post" action="{% url 'profiles:login' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Log in</button>
|
||||
</form>
|
||||
<p>No account? <a href="{% url 'profiles:register' %}">Register</a></p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,13 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Register - Packs{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Register</h1>
|
||||
<form method="post" action="{% url 'profiles:register' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
<p>Already have an account? <a href="{% url 'profiles:login' %}">Log in</a></p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,56 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Settings - Packs{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Settings</h1>
|
||||
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<h2>API Tokens</h2>
|
||||
<p>Tokens let non-browser clients (like a Fabric mod) access the API and download packs.</p>
|
||||
|
||||
<form method="post" action="{% url 'profiles:token_create' %}" class="token-create">
|
||||
{% csrf_token %}
|
||||
<input type="text" name="label" placeholder="Token label (e.g. Fabric updater)" required>
|
||||
<button type="submit">Create token</button>
|
||||
</form>
|
||||
|
||||
{% if tokens %}
|
||||
<table class="token-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Token</th>
|
||||
<th>Created</th>
|
||||
<th>Last used</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for token in tokens %}
|
||||
<tr>
|
||||
<td>{{ token.label }}</td>
|
||||
<td><code>{{ token.token }}</code></td>
|
||||
<td>{{ token.created_at }}</td>
|
||||
<td>{{ token.last_used|default:"never" }}</td>
|
||||
<td>
|
||||
<form method="post" action="{% url 'profiles:token_delete' token.id %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit">Revoke</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No tokens yet.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,14 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'profiles'
|
||||
|
||||
urlpatterns = [
|
||||
path('register/', views.register, name='register'),
|
||||
path('login/', views.user_login, name='login'),
|
||||
path('logout/', views.user_logout, name='logout'),
|
||||
path('settings/', views.settings_page, name='settings'),
|
||||
path('settings/tokens/create/', views.token_create, name='token_create'),
|
||||
path('settings/tokens/<int:token_id>/delete/', views.token_delete, name='token_delete'),
|
||||
]
|
||||
@@ -1,3 +1,70 @@
|
||||
from django.shortcuts import render
|
||||
import secrets
|
||||
|
||||
# Create your views here.
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
from .models import ApiToken, UserProfile
|
||||
|
||||
|
||||
def register(request):
|
||||
if request.method == 'POST':
|
||||
form = UserCreationForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
UserProfile.objects.get_or_create(user=user)
|
||||
login(request, user)
|
||||
messages.success(request, 'Registration successful.')
|
||||
return redirect('home')
|
||||
else:
|
||||
form = UserCreationForm()
|
||||
return render(request, 'profiles/register.html', {'form': form})
|
||||
|
||||
|
||||
def user_login(request):
|
||||
if request.method == 'POST':
|
||||
form = AuthenticationForm(request, data=request.POST)
|
||||
if form.is_valid():
|
||||
user = form.get_user()
|
||||
login(request, user)
|
||||
return redirect('home')
|
||||
else:
|
||||
form = AuthenticationForm(request)
|
||||
return render(request, 'profiles/login.html', {'form': form})
|
||||
|
||||
|
||||
def user_logout(request):
|
||||
if request.method == 'POST':
|
||||
logout(request)
|
||||
return redirect('home')
|
||||
return redirect('home')
|
||||
|
||||
|
||||
@login_required
|
||||
def settings_page(request):
|
||||
tokens = request.user.api_tokens.all()
|
||||
context = {'tokens': tokens}
|
||||
return render(request, 'profiles/settings.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def token_create(request):
|
||||
if request.method == 'POST':
|
||||
label = request.POST.get('label', '').strip() or 'API token'
|
||||
token = secrets.token_urlsafe(48)
|
||||
ApiToken.objects.create(
|
||||
user=request.user,
|
||||
token=token,
|
||||
key_prefix=token[:8],
|
||||
label=label,
|
||||
)
|
||||
messages.success(request, 'Token created.')
|
||||
return redirect('profiles:settings')
|
||||
|
||||
|
||||
@login_required
|
||||
def token_delete(request, token_id):
|
||||
ApiToken.objects.filter(id=token_id, user=request.user).delete()
|
||||
return redirect('profiles:settings')
|
||||
Reference in New Issue
Block a user