pythondjangosqliteurl-shortener

How can I create a URL-Shortener using Django based on the URL id?


I am working with Django and I am just a beginner. Now I am trying to make a URL shortener with Base 62 so I have created one class in models.py and its name is URLGenerator. Here is its code:

class URLGenerator:
   BASE = 62
   UPPERCASE_OFFSET = 55
   LOWERCASE_OFFSET = 61
   DIGIT_OFFSET = 48

   def generate_unique_key(self, integer):
      """
      Turn an integer [integer] into a base [BASE] number
       in string representation
      """

      # we won't step into the while if integer is 0
      # so we just solve for that case here
      if integer == 0:
         return '0'

     string = ""
     remainder: int = 0
     while integer > 0:
        remainder = integer % self.BASE
        string = self._true_chr(remainder) + string
        integer = int(integer / self.BASE)
    return string

   def get_id(self, key):
      """
      Turn the base [BASE] number [key] into an integer
      """
      int_sum = 0
      reversed_key = key[::-1]
      for idx, char in enumerate(reversed_key):
        int_sum += self._true_ord(char) * int(math.pow(self.BASE, idx))
    return int_sum

   def _true_ord(self, char):
      """
      Turns a digit [char] in character representation
      from the number system with base [BASE] into an integer.
      """

     if char.isdigit():
          return ord(char) - self.DIGIT_OFFSET
     elif 'A' <= char <= 'Z':
         return ord(char) - self.UPPERCASE_OFFSET
     elif 'a' <= char <= 'z':
        return ord(char) - self.LOWERCASE_OFFSET
     else:
         raise ValueError("%s is not a valid character" % char)

   def _true_chr(self, integer):
      """
      Turns an integer [integer] into digit in base [BASE]
      as a character representation.
      """
     if integer < 10:
         return chr(integer + self.DIGIT_OFFSET)
     elif 10 <= integer <= 35:
         return chr(integer + self.UPPERCASE_OFFSET)
     elif 36 <= integer < 62:
         return chr(integer + self.LOWERCASE_OFFSET)
     else:
         raise ValueError(
            "%d is not a valid integer in the range of base %d" % (integer, BASE))

The above class has two methods that are important. The first one is generate_unique_key that can convert an integer to a unique string and the second one is get_id which can convert a string to an int. For example something like that:

id = 1024
generator = URLGenerator()
key = generator.generate_unique_key(id) # key = GW
idx = generator.get_id(key)             # idx = 1024

# so the final url should look like this:
# http://localhost:8000/GW

In the above example in fact the id is the id of a record in the database which I want to use during the saving URL in the database. Then if the user writes http://127.0.0.1:8000/GW he must be redirected to the main URL which is http://127.0.0.1:8000/1024.

And I have another class which has just one field :

class Url(models.Model):
   url_id = models.AutoField(primary_key=True)

   def save(self , *args , **kwargs):
      pass

But now I do not know how to call the methods of URLGenerator in my URL class and then write a simple function using POST in views.py to see the result.

I have read some of the posts in StackOverflow and also I have read some tutorials, but unfortunately, I could not solve this problem. I will be grateful for your help and advice.


Solution

  • First, it is better to add another field to the Url model which would be something like this:

    url_id = models.AutoField(primary_key=True)
    

    And why do we have to save it? because in this question we are going to make a short URL base on the id of each record in our database. Then I create a simple form in forms.py. Here is its code:

    from django.forms import ModelForm, fields
    from .models import Url
    
    
    class URLForm(ModelForm):
        class Meta:
            model = Url
            fields = ['link']
            labels = {
                'link' : 'Enter link'
            }
    

    At last, I have two functions in my views.py. In the first one, I have used of POST method and write a simple query to save the data in the database:

    # Redirect short URL to its original URL, if it's valid
    def redirector_view(request):
        form = URLForm()
        if request.method == "POST":
            form = URLForm(request.POST)
            if form.is_valid():
                link = form.cleaned_data['link']
                if("http://" not in link) and ("https://" not in link):
                    link = "http://" + link
                # uid = 
                new_url_obj = Url(link=link)
                new_url_obj.save()
                id = new_url_obj.pk
                url_generator_obj = URLGenerator()
                sh_url = url_generator_obj.generate_unique_key(id)
                new_url = f"http://127.0.0.1:8000/url/final/{sh_url}"
                json_object = {
                    "final_url" : new_url
                }
                return JsonResponse(json_object)
        context = {
            "form" : form
        }
        return HttpResponse(context)
    

    And the second function do the exact thing which I said in my question:

    # id = 1024
    # generator = URLGenerator()
    # key = generator.generate_unique_key(id) # key = GW
    # idx = generator.get_id(key)   
    
    def final(request , sh_url):
        url_generator_obj = URLGenerator()
        id = url_generator_obj.get_id(sh_url)
        url_details = Url.objects.get(url_id=id)
        return redirect(url_details.link)
    

    And do not forget to add a urls.py file in the application :

    from django.urls import path
    from . import views
    
    urlpatterns = [
        path('', views.redirector_view, name="urlshortner"),
        path('final/<str:sh_url>' , views.final , name="final"),
    ]
    

    Now if I add a simple record to my database which just contains a URL its pk would be 1(because it is our first object) then add 1 to this URL: http://127.0.0.1:8000/url/final/ you will be redirected to the main URL.