2

i have deployed my django app in heroku, postgresql as db and for storing images i haved used amazon S3 storage, the problem what i am facing is, for creating a blog post i have used ckeditor, so user can input images along with the content text for creating a post.

enter image description here

after creating a post it looks like below

enter image description here

when right clicked on post image and open link in new tab is selected, below is the url of S3 for the image uploaded

enter image description here

after sometime images are deleted, only text content remains

enter image description here

models.py

class Post(models.Model):
    title = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE ,blank=True)
    content = RichTextUploadingField()
    date_posted = models.DateTimeField(default=timezone.now)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    thumbnail = models.ImageField(default='default_content.jpg', upload_to='content_pic')

    likes = models.ManyToManyField(User, related_name="likes", blank=True)

    def __str__(self):
        return self.title

views.py

from django.shortcuts import render,get_object_or_404,redirect
from .models import Post,Comment,Category,No_Of_Views,carousel_images_messages,about_page_details
from django.views.generic import ListView,DetailView,CreateView,UpdateView,DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin,UserPassesTestMixin
from django.contrib.auth.models import User
from .forms import CommentForm
from django.template.loader import render_to_string
from django.http import JsonResponse
from django.db.models import Count
from django.db.models import Q
from django.core.mail import send_mail
from django.contrib import messages
# Create your views here.


class PostDetailView(DetailView):
    model = Post
    context_object_name = 'post'
    def get_queryset(self):
        return Post.objects.filter(pk=self.kwargs.get('pk')).order_by("-date_posted")

    def get_context_data(self, **kwargs):
        # no of views

        no_of_views = No_Of_Views.objects.filter(post=self.kwargs.get('pk')).exists()

        #RealEstateListing.objects.filter(slug_url=slug).exists()
        if no_of_views:

            no_of_views_count = No_Of_Views.objects.get(post=self.kwargs.get('pk'))
            print(no_of_views_count.username)
            print(self.request.user)
            print(no_of_views_count.views_count)

            if no_of_views_count.username != self.request.user.username:
                no_of_views_save = int(no_of_views_count.views_count) + 1
                no_of_views_count.views_count =  no_of_views_save
                no_of_views_count.save()
            else:
                no_of_views_save = no_of_views_count.views_count

        else:
            post_title = Post.objects.get(pk=self.kwargs.get('pk'))
            print(post_title.title)


            no_of_views_save =  0
            no_of_views_for_post =  No_Of_Views(post=self.kwargs.get('pk'),post_title=post_title.title,username=post_title.author,views_count=no_of_views_save)
            no_of_views_for_post.save()


        ## ends here
        context = super().get_context_data(**kwargs)
        post = Post.objects.filter(pk=self.kwargs.get('pk')).order_by("-date_posted")
        context['comments'] = Comment.objects.filter(post=post[0],reply=None).order_by("-id")

        context['comment_form'] = CommentForm()
        is_liked = False

        if post[0].likes.filter(id=self.request.user.id).exists():
            is_liked = True



        similar_post_pk = Post.objects.filter(pk=self.kwargs.get('pk')).order_by("-date_posted")
        similar_post_pk = similar_post_pk[0]

        similar_post  = Post.objects.filter(category__category__iexact=similar_post_pk.category.category).exclude(pk=self.kwargs.get('pk')).order_by("-date_posted")[:3]
        if len(similar_post) == 0:
            similar_post  = Post.objects.all().exclude(pk=self.kwargs.get('pk')).order_by("-date_posted")[:3]
        context['is_liked'] = is_liked
        context['similar_posts'] = similar_post

        context['views'] = int(no_of_views_save)
        response = common_info()
        context['categories'] =  response["categories"]
        context['popular_posts'] =  response["popular_post"]
        title = (post[0]).title
        context['title'] = title
        #context['total_likes'] = post.total_likes()

        return context
    def post(self, request, *args, **kwargs):
        post = Post.objects.filter(pk=self.kwargs.get('pk')).order_by("-date_posted")
        comment_form = CommentForm(request.POST)
        if comment_form.is_valid():
            content = comment_form.cleaned_data["content"]
            reply_id = self.request.POST.get("comment_id")
            comment_qs = None
            if reply_id:
                comment_qs = Comment.objects.get(id=reply_id)
            comment_form = Comment.objects.create(post=post[0],user=self.request.user,content=content, reply=comment_qs)
            comment_form.save()
            return redirect("post-detail",pk=self.kwargs.get('pk'))


class PostCreateView(LoginRequiredMixin,CreateView):
    model = Post
    fields = ["title","category","content","thumbnail"]

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        response = common_info()
        context['categories'] =  response["categories"]

        context['title'] = 'Create Blog'
        return context

settings.py

import os
import django_heroku

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ["localhost"]


# Application definition

INSTALLED_APPS = [
    'users.apps.UsersConfig',
    'blog.apps.BlogConfig',
    'crispy_forms',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'storages',
    'django.contrib.humanize',
    'ckeditor',
    'ckeditor_uploader',
    'social_django',
    #'sslserver'
]

CKEDITOR_UPLOAD_PATH = 'uploads/'

CKEDITOR_CONFIGS = {
    'awesome_ckeditor': {
        'toolbar': 'Basic',
        'width':'auto'
    },
    'default': {
        'toolbar': 'full',
        'width':'auto'
    },
}

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'social_django.middleware.SocialAuthExceptionMiddleware',  # added for social login
]

ROOT_URLCONF = 'young_minds.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',

                'social_django.context_processors.backends',  # added for social login
                'social_django.context_processors.login_redirect', # added for social login
            ],
        },
    },
]


# for social login

AUTHENTICATION_BACKENDS = (
    'social_core.backends.open_id.OpenIdAuth',  # for Google authentication
    'social_core.backends.google.GoogleOpenId',  # for Google authentication
    'social_core.backends.google.GoogleOAuth2',  # for Google authentication
    'social_core.backends.github.GithubOAuth2',
    'social_core.backends.twitter.TwitterOAuth',
    'social_core.backends.facebook.FacebookOAuth2',

    'django.contrib.auth.backends.ModelBackend',
)

SOCIAL_AUTH_GITHUB_KEY = os.environ.get('SOCIAL_AUTH_GITHUB_KEY')
SOCIAL_AUTH_GITHUB_SECRET = os.environ.get('SOCIAL_AUTH_GITHUB_SECRET')

SOCIAL_AUTH_FACEBOOK_KEY = os.environ.get('SOCIAL_AUTH_FACEBOOK_KEY')  # App ID
SOCIAL_AUTH_FACEBOOK_SECRET = os.environ.get('SOCIAL_AUTH_FACEBOOK_SECRET')  # App Secret

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH2_KEY')
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET')

WSGI_APPLICATION = 'young_minds.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'mydatabase',
    }
}




# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/

STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
STATIC_URL = '/static/'





MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media")


LOGIN_REDIRECT_URL = 'blog-home'
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'blog-home'

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')


AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_S3_URL = AWS_STORAGE_BUCKET_NAME + '.s3.amazonaws.com'

#AWS_S3_URL = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_S3_REGION_NAME = "us-west-2"

AWS_S3_FILE_OVERWRITE = False
AWS_DEFAULT_ACL = None

DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
#STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

django_heroku.settings(locals())

i have used S3 for thumbnail of the post which is direct image field , this doesn't get deleted, only problem is the post images which i embedded with content using ckeditor gets deleted after sometime of uploading .

any extra information required , i will update it.

thanks in advance!

young_minds1
  • 1,181
  • 3
  • 10
  • 25
  • Are you sure the image is deletedB? Or are you just saying the URL stops working? The URL had a token with an expiration time so you likely need to regenerate that token from your server – dkarchmer Jul 20 '19 at 15:42
  • @dkarchmer how would i regenerate it? it works fine for thumbnail which is image field in models.py – young_minds1 Jul 20 '19 at 16:06
  • I don’t know without looking at your code but boto3 has functions to do so. What’s important is that in your database, you only store the bucket and key for the S3 object. – dkarchmer Jul 20 '19 at 16:08
  • If you want to store the URL ( I would not recommend ), then you probably need to change the S3 bucket to be public so tokens are not required – dkarchmer Jul 20 '19 at 16:12
  • @dkarchmer i have stored bucket and key in environment variables , which chuck of code will you need to see regeneration . i will update in body – young_minds1 Jul 20 '19 at 16:13
  • Please show your mode and your view. Are you using django-storage? – dkarchmer Jul 20 '19 at 16:14
  • @dkarchmer yes i have used django-storage, i have updated the body ,now there is models,views and settings , hope it will help you to understand my problem – young_minds1 Jul 20 '19 at 16:23
  • Image are also deleted from s3 bucket? Can you check by blog once again – giveJob Mar 04 '20 at 02:45

1 Answers1

1

I don't have experience with ckeditor_uploader, but from what you describe, it is very obvious to me that the problem is somehow related to the package storing the links to S3 directly (with the tokens on the URL) instead of storing the raw s3 bucket and key for the object, and only generating the URL on demand, each time with a new expiration time.

From doing a quick scan, the only thing I can find in the documentation is:

https://django-ckeditor.readthedocs.io/en/latest/#using-s3

where it indicates that for you to use with S3, you must have:

AWS_QUERYSTRING_AUTH = False

so I would suggest you try that.

dkarchmer
  • 5,434
  • 4
  • 24
  • 37
  • i tired doing it, doing this makes image not found in all the areas of pages where i have used S3 as storage – young_minds1 Jul 20 '19 at 16:58
  • You mean outside the CKEditor? Seems to me like that CKEditor-Uploader is incorrectly implemented. You may want to create your own. As I said, at the end, the solution should be such that in your database, you ONLY store bucket/key (that's what django-storage is supposed to do, but I don't know how CKEditor interacts with that. I personally prefer to do my own implementation instead of using django-storage. But the issue here is really CKEditor – dkarchmer Jul 20 '19 at 17:17
  • Adding this line worked for me after making my S3 bucket public (https://www.youtube.com/watch?v=UV-w3yG2zss). Thanks! – kidbilly Apr 27 '22 at 12:42