mirror of
https://github.com/wahyd4/links.git
synced 2026-08-08 21:04:53 +10:00
Add template url
This commit is contained in:
Binary file not shown.
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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.
@@ -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=默认值}。"
|
||||
|
||||
Vendored
+15
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user