loading...
Cover image for Build a News app using django

Build a News app using django

magesh236 profile image Mageshwaran ãƒŧ6 min read

In this article we will build a simple news app using django, requests, bootstrap and with the help of the news feed from NewsApi

This app is an inspiration from Google News

Installing the dependency for the project

   pip install django
   pip install requests

Setting up the django project

for detailed reference about django visit the page
Now we can create our project

   django-admin startproject newsapp
   cd newsapp

To check the project is working, run this command

   python manage.py runserver

Alt Text

You will see a rocket page that means project creation is success, even though you will see warnings is the terminal like unapplied migrations, to remove that run this command.
This command will move the default migration to database like session, user table

  python manage.py migration

In this directory you can find manage.py file and newsapp directory, after this create a app call reader

   python manage.py startapp reader

now we need to add the app reader in setting.py
this will helps django to identity the record app

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'reader',
]

Don't forget to get the Api key from https://newsapi.org/ .
Then add the api key in setting.py

APIKEY = "XXXXXXXXXXXXXXXXXXX"

View and Urls
Where we will write the logic

create a urls.py inside reader directory, after that connect the reader/urls.py file to newsapp/urls.py,
this one will act as master all the urls

from django.contrib import admin
from django.urls import path
from django.conf.urls import include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("reader.urls")),
]

NewsApi

This api will get the top news from all the sources.

url = https://newsapi.org/v2/top-headlines?country=us&apiKey=XXXXXXXXXXXXXXXXXXX

to get the data we are going to user ** python requests **

   r = requests.get(url=url) 
   data = r.json()

data will look like this

{
"status": "ok",
"totalResults": 38,
-"articles": [
-{
-"source": {
"id": null,
"name": "CNET"
},
"author": "Amanda Kooser",
"title": "Ancient underground lakes discovered on Mars - CNET",
"description": "Mars could be home to more liquid water than we originally thought.",
"url": "https://www.cnet.com/news/ancient-underground-lakes-found-on-mars/",
"urlToImage": "https://cnet2.cbsistatic.com/img/OJOEhYPBRrJmmwv03JD_1RF2wPI=/1200x630/2015/02/09/3a560b3c-d9eb-4f0c-af5c-9e9671844c93/mars-ice-cap.jpg",
"publishedAt": "2020-09-29T16:14:00Z",
"content": "This beautiful..."
}]}

Alt Text

let create a view to show the content


def home(request):
    page = request.GET.get('page', 1)
    search = request.GET.get('search', None)

    if search is None or search=="top":
        # get the top news
        url = "https://newsapi.org/v2/top-headlines?country={}&page={}&apiKey={}".format(
            "us",1,settings.APIKEY
        )
    else:
        # get the search query request
        url = "https://newsapi.org/v2/everything?q={}&sortBy={}&page={}&apiKey={}".format(
            search,"popularity",page,settings.APIKEY
        )
    r = requests.get(url=url)

    data = r.json()
    if data["status"] != "ok":
        return HttpResponse("<h1>Request Failed</h1>")
    data = data["articles"]
    context = {
        "success": True,
        "data": [],
        "search": search
    }
    # seprating the necessary data
    for i in data:
        context["data"].append({
            "title": i["title"],
            "description":  "" if i["description"] is None else i["description"],
            "url": i["url"],
            "image": temp_img if i["urlToImage"] is None else i["urlToImage"],
            "publishedat": i["publishedAt"]
        })
    # send the news feed to template in context
    return render(request, 'index.html', context=context)

the above home view will return the trending news or the requested query and then, pass it to the template, to render it in the frontend for that we need to created a template file inside /reader/templates/index.html

<!-- to get the next page content -->
<!-- in each ajax success request page value gets incremented -->
var page = 2;
<!-- we are using infinity scrolling in this page to get data -->
<!-- to prevent the unnecessary call -->
var window_scroll = true;
<!-- to get the news related to the page on -->
var search = "{{ search }}";
<!--  this will listen when scrolling -->
window.addEventListener('scroll', function(e) {
   console.log("Hi Dude...");
}
<!DOCTYPE html>
<html lang="en">
<head>
  <title>News App</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<body>

    <div>
        {% include 'sidebar.html' %}
    </div>
    <div class="container" id="articles-container">
        {% for value in data %}
        <div class="card mb-3 box" style="max-width: 640px; margin:auto;">
            <div class="row">
                <div class="col-md-8">
                    <div class="card-body">
                        <h5 class="card-title"><a href="{{value.url}}" target="_blanck">{{value.title}}</a></h5>
                        <p class="card-text">{{value.description}}</p>
                        <p class="card-text"><small class="text-muted">{{value.publishedat}}</small></p>
                    </div>
                </div>

                <div class="col-md-4 img-box">
                    <img src="{{value.image}}" class="card-img" alt="..." height="100%">
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
    <h1 id="loading">Loading....</h1>

</body>
<style>
#articles-container{
    margin-left: 250px;
}
.box{
    transition-property: box-shadow, transform;
    transition-duration: 1s;
}
.box:hover{
    /* box-shadow: 2px 2px 5px #8080807a; */
    box-shadow: 1px 2px 8px 0px #00bcffb0;
    transform: translateY(-9px);
}
.img-box{
    padding: 20px;
}
.img-box img{
    border-radius: 15px;
    object-fit: cover;
}
#loading{
    text-align: center;
    display: none;
}
</style>
<script>

$(document).ready(function() {
    console.log( "ready!" );
    el = document.getElementsByClassName("text-muted")
    for (i = 0; i < el.length; i++) {
        t = document.getElementsByClassName("text-muted")[i].innerText
        var d = new Date(t)
        document.getElementsByClassName("text-muted")[i].innerText = d.toDateString()
    }
});
var page = 2;
var window_scroll = true;
var search = "{{ search }}";

window.addEventListener('scroll', function(e) {
    // scroll check
    console.log("scroll check...")
    if(window_scroll){
        if((window.innerHeight + window.scrollY) >= (document.body.scrollHeight-200)){
            window_scroll = false;
            document.getElementById("loading").style.display = "block";
            $.ajax({
                url: '/next?page=' + page + '&search=' + search,
                dataType: 'json',
                success: function (data) {
                    if (data["success"]) {
                        articles = data["data"]
                        articles_html = ''
                        for (i = 0; i < articles.length; i++) {
                            articles_html += ' \
                            <div class="card mb-3 box" style="max-width: 640px; margin:auto;">\
                                <div class="row">\
                                    <div class="col-md-8">\
                                        <div class="card-body">\
                                            <h5 class="card-title"><a href="'+ articles[i]["url"] +'" target="_blanck">'+ articles[i]["title"] +'</a></h5>\
                                            <p class="card-text">'+ articles[i]["description"] +'</p>\
                                            <p class="card-text"><small class="text-muted">'+ articles[i]["publishedat"] +'</small></p>\
                                        </div>\
                                    </div>\
                                            \
                                    <div class="col-md-4 img-box">\
                                        <img src="'+ articles[i]["image"] +'" class="card-img" alt="..." height="100%">\
                                    </div>\
                                </div>\
                            </div>\
                            '
                        }
                        $("#articles-container").append(articles_html);
                        page += 1
                        window_scroll = true;
                        document.getElementById("loading").style.display = "none";
                    }
                    else {
                        console.log("Failed")
                        window_scroll = true;
                        document.getElementById("loading").style.display = "none";
                    }
                }
            });
        }
    }
})
</script>
</html>

after this create a another template file inside /reader/template/sidebar.html

django way of send the query params in Get method

<a href="{% url 'Home' %}?search=Business">Business</a>
<style>
    .sidebar{
        position: fixed;
        width: 200px;
        height: 100%;
        top: 0px;
        left: 0px;
    }
    .sidebar a{
        width: 250px;
        display: block;
        margin: 24px 0px;
        padding: 10px 5px;
        font-weight: bolder;
        font-size: large;
        text-decoration: none;
        background-color: #d9e4ffc7;
        border-top-right-radius: 30px;
        border-bottom-right-radius: 30px;
        box-shadow: 0px 0.5px 1px grey;
        text-align: center;
    }
</style>

<div class="sidebar">
    <a href="{% url 'Home' %}">Home</a>
    <a href="{% url 'Home' %}?search=top">Top News</a>

    <a href="{% url 'Home' %}?search=Business">Business</a>
    <a href="{% url 'Home' %}?search=Technology">Technology</a>
    <a href="{% url 'Home' %}?search=Entertainment">Entertainment</a>
    <a href="{% url 'Home' %}?search=Sports">Sports</a>
    <a href="{% url 'Home' %}?search=Science">Science</a>
    <a href="{% url 'Home' %}?search=Health">Health</a>
</div>

In the home view function it render the html page, it also require an additional api to update the news while the user scrolls the page, this will make the web more intractable without loading the entire page.
Use ajax request to get the json value from this loadcontent api

def loadcontent(request):
    try:
        page = request.GET.get('page', 1)
        search = request.GET.get('search', None)
        # url = "https://newsapi.org/v2/everything?q={}&sortBy={}&page={}&apiKey={}".format(
        #     "Technology","popularity",page,settings.APIKEY
        # )
        if search is None or search=="top":
            url = "https://newsapi.org/v2/top-headlines?country={}&page={}&apiKey={}".format(
                "us",page,settings.APIKEY
            )
        else:
            url = "https://newsapi.org/v2/everything?q={}&sortBy={}&page={}&apiKey={}".format(
                search,"popularity",page,settings.APIKEY
            )
        print("url:",url)
        r = requests.get(url=url)

        data = r.json()
        if data["status"] != "ok":
            return JsonResponse({"success":False})
        data = data["articles"]
        context = {
            "success": True,
            "data": [],
            "search": search
        }
        for i in data:
            context["data"].append({
                "title": i["title"],
                "description":  "" if i["description"] is None else i["description"],
                "url": i["url"],
                "image": temp_img if i["urlToImage"] is None else i["urlToImage"],
                "publishedat": i["publishedAt"]
            })

        return JsonResponse(context)
    except Exception as e:
        return JsonResponse({"success":False})

we made the view and templates, to access it we need to provide the urls

from django.urls import path
from reader import views

urlpatterns = [
    path('', views.home, name="Home"),
    path('next', views.loadcontent, name="Loadcontent"),
]

You can use this api only to get the Json data, send the query params accordingly

   path('next', views.loadcontent, name="Loadcontent"),

Now run the app, you will see
Alt Text
Finally we made it
Alt Text

This is the project structure
Alt Text

Thanks you, have a great day ahead.ðŸĪŠðŸ˜Ž

Posted on by:

magesh236 profile

Mageshwaran

@magesh236

Software Engineer - Newbie Photography

Discussion

pic
Editor guide