Add template url

This commit is contained in:
2024-11-02 22:13:38 +11:00
parent 0eeba66b38
commit 55210ddeb8
10 changed files with 149 additions and 7 deletions
BIN
View File
Binary file not shown.
+24
View File
@@ -2,6 +2,8 @@ from django import forms
from .models import Link
from simplemde.fields import SimpleMDEField
from django.utils.translation import gettext_lazy as _
from django.core.validators import URLValidator
import re
class LinkForm(forms.ModelForm):
text = SimpleMDEField()
@@ -14,6 +16,28 @@ class LinkForm(forms.ModelForm):
'description': forms.Textarea(attrs={'rows': 3}),
}
def clean_original_url(self):
url = self.cleaned_data.get('original_url')
if not url:
return url
# Extract template parameters
template_params = re.findall(r'\{([^{}]*)\}', url)
# Temporarily replace template parameters with placeholder
temp_url = url
for param in template_params:
param_full = '{' + param + '}'
temp_url = temp_url.replace(param_full, 'template-param')
# Validate the URL with placeholders
try:
URLValidator()(temp_url)
except forms.ValidationError:
raise forms.ValidationError(_("Please enter a valid URL. Template parameters are allowed in the format {param_name,default=value}."))
return url
def clean(self):
cleaned_data = super().clean()
link_type = cleaned_data.get('link_type')
@@ -0,0 +1,18 @@
# Generated by Django 5.0.8 on 2024-11-02 11:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('links', '0007_link_description'),
]
operations = [
migrations.AlterField(
model_name='link',
name='original_url',
field=models.TextField(blank=True, null=True),
),
]
+50 -2
View File
@@ -2,6 +2,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
import logging
import re
logger = logging.getLogger(__name__)
@@ -11,8 +12,8 @@ class Link(models.Model):
CUSTOM = 'CUSTOM', _('Custom')
alias = models.SlugField(max_length=100, unique=True)
original_url = models.URLField(blank=True, null=True)
text = models.TextField(blank=True, null=True) # 改为 TextField
original_url = models.TextField(blank=True, null=True)
text = models.TextField(blank=True, null=True)
link_type = models.CharField(
max_length=10,
choices=LinkType.choices,
@@ -23,6 +24,53 @@ class Link(models.Model):
updated_at = models.DateTimeField(auto_now=True)
description = models.TextField(blank=True, null=True, verbose_name=_("Description"))
def get_template_parameters(self):
"""Extract template parameters and their default values from original_url"""
pattern = r'\{([^{}]*)\}'
matches = re.finditer(pattern, self.original_url)
params = {}
for match in matches:
param_str = match.group(1)
if ',' in param_str:
param_name, default_value = param_str.split(',', 1)
default_value = default_value.strip()
if default_value.startswith('default='):
default_value = default_value[8:].strip('"\'')
params[param_name.strip()] = default_value
else:
params[param_str.strip()] = None
return params
def get_processed_url(self, **kwargs):
"""Process template URL with provided parameters"""
if not self.original_url:
return None
processed_url = self.original_url
pattern = r'\{([^{}]*)\}'
matches = re.finditer(pattern, self.original_url)
for match in matches:
param_str = match.group(1)
if ',' in param_str:
param_name, default_value = param_str.split(',', 1)
default_value = default_value.strip()
if default_value.startswith('default='):
default_value = default_value[8:].strip('"\'')
else:
param_name = param_str.strip()
default_value = None
value = kwargs.get(param_name, default_value)
if value is None:
raise ValueError(f"No value provided for parameter {param_name}")
processed_url = processed_url.replace(f"{{{param_str}}}", str(value))
return processed_url
def __str__(self):
return self.alias
+8
View File
@@ -45,6 +45,14 @@
{% trans "Custom type allows you to create a link with custom HTML content. Use this when you want to display a message or custom content instead of redirecting." %}
</p>
</div>
<div id="template-description" class="bg-purple-50 border-l-4 border-purple-400 p-4 mb-4 {% if is_custom %}hidden{% endif %}">
<p class="text-sm text-purple-700">
{% trans "Template type allows you to create dynamic URLs with parameters. Use {query, default=value} syntax in the URL." %}
</p>
<p class="text-sm text-purple-700 mt-2">
{% trans "Example: https://google.com/search?q={query, default=hello}" %}
</p>
</div>
<!-- Form -->
<form method="post" class="space-y-6">
+1
View File
@@ -13,6 +13,7 @@ urlpatterns = [
path('link/<int:pk>/edit/', views.LinkUpdateView.as_view(), name='link_update'),
path('tools/', views.ToolsView.as_view(), name='tools'),
path('<str:alias>/', views.redirect_to_original, name='redirect_to_original'),
path('<str:alias>/<str:param>/', views.redirect_to_original, name='redirect_to_original_with_param'),
path('export/', views.export_links, name='export_links'),
# New route for link detail view by alias
+29 -4
View File
@@ -18,6 +18,7 @@ import random
from django.utils.text import slugify
import markdown
import logging
import re
logger = logging.getLogger(__name__)
@@ -178,16 +179,40 @@ def delete_selected(request):
Link.objects.filter(id__in=selected_ids).delete()
return redirect('link_list')
def redirect_to_original(request, alias):
# 将别名转换为小写并移除特殊字符
def redirect_to_original(request, alias, param=None):
processed_alias = ''.join(e for e in alias.lower() if e.isalnum())
try:
link = Link.objects.get(alias=processed_alias)
link.click_count = F('click_count') + 1
link.save()
ClickLog.objects.create(link=link) # 记录点击
return redirect(link.original_url)
ClickLog.objects.create(link=link)
try:
# 如果是模板 URL 并且提供了参数
if param:
# 从 URL 中提取参数名
pattern = r'\{([^{}]*)\}'
matches = re.finditer(pattern, link.original_url)
for match in matches:
param_str = match.group(1)
if ',' in param_str:
param_name = param_str.split(',')[0].strip()
else:
param_name = param_str.strip()
# 使用提供的参数值
url = link.get_processed_url(**{param_name: param})
return redirect(url)
# 如果没有提供参数,使用默认值
url = link.get_processed_url()
if url is None:
url = link.original_url
return redirect(url)
except ValueError as e:
messages.error(request, str(e))
return redirect('link_list')
except Link.DoesNotExist:
messages.warning(request, _("The alias '{}' doesn't exist. Do you want to create a new one?").format(processed_alias))
return redirect(reverse('link_create') + f'?alias={processed_alias}')
Binary file not shown.
+4 -1
View File
@@ -1856,7 +1856,7 @@ msgstr "无效的JSON格式。预期是对象列表。"
#: links/views.py:282
msgid ""
"Invalid item in JSON. Each item must be an object with an 'alias' field."
msgstr "JSON中存在无效项。每个项必须是包含'alias'字段的对象。"
msgstr "JSON中存在无效项。每个项必须是包含'alias'字段的对象。"
#: links/views.py:295
#, fuzzy, python-brace-format
@@ -1910,3 +1910,6 @@ msgstr "简体中文"
msgid "Description"
msgstr "描述"
msgid "Please enter a valid URL. Template parameters are allowed in the format {param_name,default=value}."
msgstr "请输入有效的URL。允许使用模板参数,格式为 {参数名,default=默认值}。"
+15
View File
@@ -1039,6 +1039,11 @@ video {
border-color: rgb(74 222 128 / var(--tw-border-opacity));
}
.border-purple-400 {
--tw-border-opacity: 1;
border-color: rgb(192 132 252 / var(--tw-border-opacity));
}
.border-transparent {
border-color: transparent;
}
@@ -1098,6 +1103,11 @@ video {
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
}
.bg-purple-50 {
--tw-bg-opacity: 1;
background-color: rgb(250 245 255 / var(--tw-bg-opacity));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
@@ -1352,6 +1362,11 @@ video {
color: rgb(79 70 229 / var(--tw-text-opacity));
}
.text-purple-700 {
--tw-text-opacity: 1;
color: rgb(126 34 206 / var(--tw-text-opacity));
}
.text-red-600 {
--tw-text-opacity: 1;
color: rgb(220 38 38 / var(--tw-text-opacity));